Простая цепочка фильтров (полный код внизу поста).
Допустим, у меня есть страница входа, которая предоставляет имя пользователя и пароль.
Запрос проходит через фильтр аутентификации, который проверяетучетные данные и, если он проверяется, добавляет пользовательский объект в качестве атрибута к запросу.
@WebFilter(filterName = "AuthFilter",urlPatterns = "/secret")
public class AuthFilter implements Filter {...}
Затем запрос проходит через фильтр предупреждений, который примет этот атрибут и будет использовать его для регистрации доступа пользователякомпонент.
@WebFilter(filterName = "SecurityWarningFilter",urlPatterns = "/secret")
public class SecurityWarningFilter implements Filter { ... }
Я сейчас пытаюсь заставить NPE преднамеренно соединить их в неправильном порядке.Поэтому SecurityWarningFilter
должен сначала обработать запрос, попытаться обработать атрибут, которого еще нет, и выдать исключение.
Я посмотрел на Как определить порядок фильтра сервлетоввыполнение с использованием аннотаций в WAR и начиная с
Фильтры вызываются в том порядке, в котором отображения фильтров появляются в списке отображений фильтров WAR.~ Учебник по сервлетам
Это то, что я пробил в web.xml
:
<filter-mapping>
<filter-name>SecurityWarningFilter</filter-name>
<url-pattern />
</filter-mapping>
<filter-mapping>
<filter-name>AuthFilter</filter-name>
<url-pattern />
</filter-mapping>
Это ничего не делает, хотя.AuthFilter
по-прежнему сначала обрабатывает запрос, и только если он передает его в цепочку, SecurityWarningFilter
выполняет свою задачу.
Почему это так?И как мне заставить NPE?
Обратите внимание, что если я закомментирую аннотации и вместо этого перейду к полному определению xml:
<filter>
<filter-name>AuthFilter</filter-name>
<filter-class>[...].webapp.filters.AuthFilter</filter-class>
</filter>
<filter>
<filter-name>SecurityWarningFilter</filter-name>
<filter-class>[...].webapp.filters.SecurityWarningFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SecurityWarningFilter</filter-name>
<url-pattern>/secret</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>AuthFilter</filter-name>
<url-pattern>/secret</url-pattern>
</filter-mapping>
ТОГДА я получаю NPE, которое ищу,(И переключение порядка, в котором я определяю теги <filter-mapping>
, избавляется от него снова.) Но я бы очень предпочел использовать аннотации для определений фильтров вместо <filter>
тегов.
IИспользую Apache Tomcat/7.0.47
.Любая помощь будет принята с благодарностью.
(Кроме того, всем вам счастливого Рождества.)
ОБНОВЛЕНИЕ Похоже, что если я упомянуШаблоны URL в XML, я могу принудительно использовать NPE при использовании аннотаций:
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0" metadata-complete="false"
>
<!-- this mapping forces an NPE -->
<filter-mapping>
<filter-name>SecurityWarningFilter</filter-name>
<url-pattern>/secret</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>AuthFilter</filter-name>
<url-pattern>/secret</url-pattern>
</filter-mapping>
<!-- other servlets here -->
</web-app>
, что указывает на то, что есть что-то в <url-pattern />
, которое нуждается в некоторой настройке - поэтому он берет шаблоны URL изаннотации - что я еще не сделал.
Есть идеи?
код:
login.jsp
<html>
<body>
<form action='/webapp/secret' method='post'>
username: <input type='text' name ='username'><br>
password: <input type='password' name ='password'><br>
<input type='submit', value='login'>
</form>
</body>
</html>
web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Webapp</display-name>
<filter-mapping>
<filter-name>SecurityWarningFilter</filter-name>
<url-pattern />
</filter-mapping>
<filter-mapping>
<filter-name>AuthFilter</filter-name>
<url-pattern />
</filter-mapping>
<!-- other servlets here -->
</web-app>
РЕДАКТИРОВАТЬ : согласно предложению Стива (спасибо), это было обновлено до
new web.xml
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0" metadata-complete="false"
>
<!-- this mapping forces an NPE -->
<filter-mapping>
<filter-name>SecurityWarningFilter</filter-name>
<url-pattern />
</filter-mapping>
<filter-mapping>
<filter-name>AuthFilter</filter-name>
<url-pattern />
</filter-mapping>
<!-- other servlets here -->
</web-app>
AuthFilter.java
package [...].webapp.filters;
import [...].security.Credentials;
import [...].webapp.consts.AuthConstants;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Set;
@WebFilter(filterName = "AuthFilter",urlPatterns = "/secret")
public class AuthFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
String username = req.getParameter("username");
String password = req.getParameter("password");
if(username == null || password == null){
PrintWriter out = resp.getWriter();
out.println("access denied");
return;
}
Credentials creds = new Credentials(username,password, false);
if(validate(creds)){
req.setAttribute(AuthConstants.ATTR_ACTIVE_USER,creds);
chain.doFilter(req,resp);
} else{
PrintWriter out = resp.getWriter();
out.println("username or pasword is incorrect");
}
}
private boolean validate(Credentials creds){
Set<Credentials> acceptedUsers = getAcceptedUsers();
return acceptedUsers.contains(creds);
}
private Set<Credentials> getAcceptedUsers(){
//imagine a proper fetch, e.g. from DB or some cache, here
return new HashSet<Credentials>(){{add(new Credentials("foo","bar", false));}};
}
@Override
public void destroy() {}
}
SecurityWarningFilter.java
package [...].webapp.filters;
import [...].security.Credentials;
import [...].webapp.consts.AuthConstants;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.util.Date;
@WebFilter(filterName = "SecurityWarningFilter",urlPatterns = "/secret")
public class SecurityWarningFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
Credentials accessingUser = (Credentials)req.getAttribute(AuthConstants.ATTR_ACTIVE_USER);
doSecurityWarning(accessingUser);
chain.doFilter(req,resp);
}
private void doSecurityWarning(Credentials accessingUser) {
String timestamp = new Date().toString();
//imagine some proper logging, here
System.err.println(String.format("WARNING[%s] access to secured resource by user '%s'",timestamp,accessingUser.username));
}
@Override
public void destroy() {}
}
SecretServlet.java
package [...].webapp.servlets;
import [...].security.Credentials;
import [...].webapp.consts.AuthConstants;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet("/secret")
public class SecretServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
serveRequest(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
serveRequest(req, resp);
}
private void serveRequest(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Credentials authorisedUser = (Credentials)req.getAttribute(AuthConstants.ATTR_ACTIVE_USER);
resp.getWriter().println(String.format("You are authorised. Welcome %s.",authorisedUser.username));
}
}
Credentials.java
package [...].security;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
public class Credentials{
public final String username;
final String password;
public Credentials(String username, String password, boolean isPasswordHashed) {
this.username = username;
if(isPasswordHashed) this.password = password;
else {
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
md.update(password.getBytes());
byte[] hash = md.digest();
this.password = (new HexBinaryAdapter()).marshal(hash);
}
}
@Override
public boolean equals(Object obj) {
if(obj == null) return false;
if(!(obj instanceof Credentials)) return false;
Credentials other = (Credentials)obj;
return this.username.equals(other.username) && this.password.equals(other.password);
}
@Override
public int hashCode() {
return Objects.hash(username,password);
}
@Override
public String toString() {
return String.format("[\n\t%s\n\t%s\n]", username,password);
}
}