首页 > 解决方案 > 使用注释时忽略“web.xml”中的过滤顺序?

问题描述

简单的过滤器链(帖子底部的完整代码)。

假设我有一个提供用户名和密码的登录页面。

请求通过一个检查凭据的身份验证过滤器,如果它签出,则将用户对象作为属性添加到请求中。

@WebFilter(filterName = "AuthFilter",urlPatterns = "/secret")
public class AuthFilter implements Filter {...}

然后请求通过一个警告过滤器,该过滤器将采用该属性并使用它来记录用户访问了该组件。

@WebFilter(filterName = "SecurityWarningFilter",urlPatterns = "/secret")
public class SecurityWarningFilter implements Filter { ... }

我现在一直试图通过故意以错误的顺序连接它们来强制 NPE。所以SecurityWarningFilter应该先处理请求,尝试对尚不存在的属性进行操作并抛出异常。

我已经查看了How to define servlet filter order of execution using annotations in WAR并且因为

按照过滤器映射出现在 WAR 的过滤器映射列表中的顺序调用过滤器。~ Servlet 教程

这就是我打入的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>标签。

我正在使用Apache Tomcat/7.0.47. 任何帮助将不胜感激。

(另外,祝大家圣诞快乐。)

更新 似乎如果我在 xml 中提到 url 模式,我可以在使用注释时强制 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 模式——我还没有完成。

有任何想法吗?

代码:

登录.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>

编辑:根据史蒂夫的建议(谢谢),这已更新为

新的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() {}

}

安全警告过滤器.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));
    }
}

凭据.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);
    }
}

标签: javatomcatservletsjakarta-eeservlet-filters

解决方案


我不相信在任何 servlet 规范(最高 4.0)下都可以实现您想要的。

元素的 XSDfilter-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>

这表明过滤器映射必须至少包含 aurl-pattern或 a之一servlet-name

此外,这:

  <url-pattern />

相当于:

  <url-pattern></url-pattern>

规范(§12.2)规定:

空字符串 ("") 是一种特殊的 URL 模式,它精确映射到应用程序的上下文根......

换句话说<url-pattern />,将始终覆盖您在@WebFilter注释中声明的任何模式,因为 XML 声明总是取代注释。

因此,如果您需要特定的过滤器排序,那么您必须在 web.xml 中以所需的顺序声明完整filter-mapping的元素,包括正确的元素。url-pattern

顺便说一句,您的 web.xml 部署描述符的标头用于更旧版本的 servlet 规范。

对于 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"实际上是默认值。


推荐阅读