java - 使用注释时忽略“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);
}
}
解决方案
我不相信在任何 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"
实际上是默认值。
推荐阅读
- python - 如何减少带有 4M+ 字符串对象的字典的内存占用?
- python - 在 Golang 的 Tensorflow 中打开带有嵌入层的 Keras 模型
- json - JSON 到 JSON-LD (+ Schema.org) 的转换
- ios - WKWebView 禁用水平滚动和换行内容
- python - python中使用列表启动类项
- ios - 无法在 UISearchController 上获得 becomeFirstResponder 工作
- html - 在 CSS 网格中使用绝对/固定定位
- python - 带有导入函数的 sklearn FunctionTransfromer
- swift - 使用 AVAudioPCMBuffer 的 AVAudioPlayerNode - 不播放音频
- office-ui-fabric - 设置 Office UI Fabric TextField 的最大宽度