使用注释时会忽略“ web.xml”中的过滤顺序吗?

时间:2018-12-24 14:22:34

标签: java tomcat servlets java-ee servlet-filters

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

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

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

@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 Tutorial

这就是我打入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传递给链后,<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?

请注意,如果我注释掉了注释,而是使用了完整的xml定义:

<filter-mapping>

当我获得所需的NPE时。 (并且颠倒了我定义<filter>标签的顺序,这又使它摆脱了。) 但是我非常希望对过滤器定义使用注释,而不是Apache Tomcat/7.0.47标签。

我正在使用<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> 。 任何帮助将不胜感激。

(也祝大家圣诞快乐。)

更新 看来,如果我在xml中提到url模式,则可以在使用注释时强制使用NPE:

<url-pattern />

这向我表明<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> 上有一些东西需要一些配置-因此它需要使用注释中的url模式-我还没有做过。

有什么想法吗?

代码:

login.jsp

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

EDIT :根据史蒂夫的建议(谢谢),此内容已更新为

新的 web.xml

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

}

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

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

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

Credentials.java

@Override
public void onBindViewHolder(final RecyclerViewHolder viewHolder, final int i) {

    foodListDetail ft = foodList.get(i);
    viewHolder.tvFood.setText(ft.getFood());
    viewHolder.tvCost.setText(String.valueOf(ft.getCost())+" บาท");
    viewHolder.tvunit.setText(ft.getUnit());
    Picasso.get()
            .load(ft.getUrl())
            .placeholder(R.drawable.ic_android_black_24dp)
            .error(R.drawable.ic_error_black_24dp)
            .into(viewHolder.img);
    viewHolder.cd.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            final Dialog myDialog = new Dialog(ctx);
                myDialog.setContentView(R.layout.popup1);
                ImageView img = myDialog.findViewById(R.id.imageView);
                final TextView tvName = (TextView)myDialog.findViewById(R.id.foodName);
                final TextView tvCost = (TextView)myDialog.findViewById(R.id.cost);
                TextView tvClose = (TextView)myDialog.findViewById(R.id.tvClose);
                TextView tvNum = (TextView)myDialog.findViewById(R.id.num);
                ImageView up = myDialog.findViewById(R.id.up);
                ImageView down = myDialog.findViewById(R.id.down);
                myDialog.show();
        }
    });

}

1 个答案:

答案 0 :(得分:1)

我认为在任何servlet规范(最高4.0)下都无法实现您想要的。

filter-mapping元素的XSD包含以下内容:

  <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-patternservlet-name中的至少一个。

此外,这个:

  <url-pattern />

等效于:

  <url-pattern></url-pattern>

规范(第12.2节)规定:

  

空字符串(“”)是一种特殊的网址格式,它精确地映射到   应用程序的上下文根...

换句话说,<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"实际上是默认值。

相关问题