Порядок фильтра в `web.xml` игнорируется при использовании аннотаций? - PullRequest
0 голосов
/ 24 декабря 2018

Простая цепочка фильтров (полный код внизу поста).

Допустим, у меня есть страница входа, которая предоставляет имя пользователя и пароль.

Запрос проходит через фильтр аутентификации, который проверяетучетные данные и, если он проверяется, добавляет пользовательский объект в качестве атрибута к запросу.

@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);
    }
}

1 Ответ

0 голосов
/ 26 декабря 2018

Я не верю, что можно достичь того, чего вы хотите, по любой из спецификаций сервлета (до 4.0).

XSD для элемента filter-mapping содержит следующее:

  <xsd:choice minOccurs="1"
              maxOccurs="unbounded">
    <xsd:element name="url-pattern"
                 type="javaee:url-patternType"/>
    <xsd:element name="servlet-name"
                 type="javaee:servlet-nameType"/>
  </xsd:choice>

Это означает, что отображение фильтра должно содержать хотя бы одно из значений: url-pattern или servlet-name.

Кроме того, это:

  <url-pattern />

эквивалент:

  <url-pattern></url-pattern>

Спецификации (§12.2) утверждают, что:

Пустая строка ("") - это специальный шаблон URL, который точно отображается в контексте приложенияroot ...

Другими словами <url-pattern /> всегда будет переопределять любые шаблоны, которые вы объявили в своих @WebFilter аннотациях, потому что объявления XML всегда заменяют аннотации.

Поэтому, если вам нужен определенный порядок фильтров, вы должны объявить полные элементы filter-mapping, включая правильные элементы url-pattern в требуемом порядке, в файле web.xml.

Кстати, заголовок вашегоразвертывание web.xmlдескриптор предназначен для гораздо более старой версии спецификации сервлета.

Для Tomcat 7.x он должен быть следующим:

<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" >

metadata-complete="false" фактически является значением по умолчанию.

...