- A+
本文于2016年4月底完成,发布在个人博客网站上。
考虑个人博客因某种原因无法修复,于是在博客园安家,之前发布的文章逐步搬迁过来。
诡异的问题
AppScan扫描报告中提示,Web服务器返回js
、css
、png
、jsp
页面的HTTP响应中缺少安全头部。HTTP的安全头部包括HTTP Strict Transport Security
、X-Frame-Options
、X-Content-Type-Options
、X-XSS-Protection
、Content-Security-Policy
。
网上资料很多,于是参照资料修改$CATALINA_BASE/conf/web.xml
,增加相关配置,如下是样例:
<filter> <filter-name>httpHeaderSecurity</filter-name> <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class> <async-supported>true</async-supported> </filter> <filter-mapping> <filter-name>httpHeaderSecurity</filter-name> <url-pattern>/*</url-pattern> <!-- 注意:Jackie遇到的问题与httpHeaderSecurity的配置相关。 --> </filter-mapping>
本以为这样修改之后问题就解决了,所以也没用浏览器的调试面板去仔细检查Web服务器响应数据的HTTP头部;但天不遂人愿,事情并没有如预想的方向发展。
在稍后的一份AppScan扫描报告中,居然又看到了Web服务器返回的HTTP响应缺少安全头部的提示。不过这次稍有区别,报告中只提示jsp
页面的访问存在问题。于是使用浏览器的调试面板仔细查看Web服务器返回的响应信息,发现Web服务器返回js
、css
、png
时,在HTTP响应中增加了必要的头部,如下所示:
Cache-Control:private Content-Type:image/png Date:Sun, 10 Apr 2016 13:16:26 GMT Expires:Thu, 01 Jan 1970 08:00:00 CST Server:Apache-Coyote/1.1 Strict-Transport-Security:max-age=0 Transfer-Encoding:chunked X-Content-Type-Options:nosniff X-Frame-Options:DENY X-XSS-Protection:1; mode=block
这说明安全头部的配置生效了,但诡异的是jsp
页面的响应中并没有相应增加安全头部,如下所示,导致AppScan报告中Web服务器返回的HTTP响应缺少安全头部问题依然存在。
Content-Type:text/html;charset=UTF-8 Date:Tue, 24 May 2016 16:18:30 GMT Server:Apache-Coyote/1.1 Transfer-Encoding:chunked
同部门内有一个A项目,这个项目有10年开发、维护的历史,历经公司安全红线多轮整改,项目成员积累了相当丰富的斗争经验,在处理AppScan扫描报告上也有相当的经验。于是就安全头部的整改方法咨询A项目的MDE,希望可以获得关键信息。
A项目的MDE为人很爽快,介绍了他们的经验,总结下有如下几点:
- A项目在整改AppScan扫描问题时,确实遇到过类似的问题,解决的方法是给响应增加安全头部。
- 但A项目使用了自定义的过滤器来给HTTP响应增加安全头部,并没有使用Apache Tomcat官方提供的过滤器,原因是A项目使用的Tomcat版本太低,出于业务原因暂不好升级。
- A项目增加自定义的过滤器之后,“Web服务器返回的HTTP响应缺少安全头部”就从AppScan扫描报告中消失了。
但坏消息是A项目团队没有遇到过前述的问题,自然没有处理类似问题的经验可供参考。这就诡异了,为什么Web服务器对jsp
的响应没有增加安全头部呢?
分析过程
当前的项目使用了Spring+Struts2+iBatis,从技术组合上可以说非常传统,但在技术应用上存在很大不同。为了描述方便,下面把存在问题的项目称为B项目。
检查项目配置
重温项目的配置情况。
Struts2的配置
Struts2在web.xml中的配置如下:
<filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
简化后的struts.xml配置文件,内容如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <constant name="struts.enable.DynamicMethodInvocation" value="false" /> <constant name="struts.devMode" value="true" /> <constant name="struts.action.extension" value="jsp,action"/> <!-- 注意这里 --> <constant name="struts.ui.theme" value="java"></constant> <constant name="struts.objectFactory" value="spring" /> <constant name="struts.i18n.encoding" value="UTF-8" /> <package name="default" namespace="/" extends="struts-default"> <interceptors> <interceptor-stack name="myStack"> <interceptor-ref name="basicStack"></interceptor-ref> </interceptor-stack> </interceptors> <default-interceptor-ref name="myStack" /> <global-results> <result name="error">/error.jsp</result> </global-results> <global-exception-mappings> <exception-mapping exception="java.lang.Exception" result="error"/> </global-exception-mappings> <action name="*" class="MainAction"> <result name="success">{1}.jsp</result> </action> </package> </struts>
通用Action类,简化后的MainAction
代码如下
import com.opensymphony.xwork2.ActionSupport; public class MainAction extends ActionSupport { private static final long serialVersionUID = 928135783255954591L; @Override public String execute() throws Exception { return ActionSupport.SUCCESS; } }
粗看下来,似乎没有什么不妥的地方。
安全头部的配置
依照文档,重新检查$CATALINA_BASE/conf/web.xml
文件中的配置,如下:
<filter> <filter-name>httpHeaderSecurity</filter-name> <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class> <async-supported>true</async-supported> </filter> <filter-mapping> <filter-name>httpHeaderSecurity</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
没看出来什么特别的地方,而官方文档对HttpHeaderSecurityFilter
的使用也没有特别的说明,那是不是HttpHeaderSecurityFilter
的实现代码中有玄机?
找到HttpHeaderSecurityFilter
类的代码,如下是增加头部的实现。
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (response instanceof HttpServletResponse) { HttpServletResponse httpResponse = (HttpServletResponse) response; if (response.isCommitted()) { throw new ServletException(sm.getString("httpHeaderSecurityFilter.committed")); } // HSTS if (hstsEnabled && request.isSecure()) { httpResponse.setHeader(HSTS_HEADER_NAME, hstsHeaderValue); } // anti click-jacking if (antiClickJackingEnabled) { httpResponse.setHeader(ANTI_CLICK_JACKING_HEADER_NAME, antiClickJackingHeaderValue); } // Block content type sniffing if (blockContentTypeSniffingEnabled) { httpResponse.setHeader(BLOCK_CONTENT_TYPE_SNIFFING_HEADER_NAME, BLOCK_CONTENT_TYPE_SNIFFING_HEADER_VALUE); } // cross-site scripting filter protection if (xssProtectionEnabled) { httpResponse.setHeader(XSS_PROTECTION_HEADER_NAME, XSS_PROTECTION_HEADER_VALUE); } } chain.doFilter(request, response); }
代码很简单,没发现对jsp的访问有做过什么特别的处理。
对页面访问的影响
依据前述配置,页面访问流程如下所示:
- 浏览器请求页面时,Web服务端的Struts2拦截页面访问请求;
- Web服务端的通用Action接收请求,并将请求重定向至对应的jsp页面;
- 由于没有使用Action向页面传递数据,所以开发人员需要在页面上使用ajax方式向Web服务端请求业务数据;
进一步分析
仔细回想了A项目的特点,以及与B项目的差异点。
A项目也使用了Spring+Struts的组合,但和B项目有个显著不同点,B项目是Struts2的重度使用用户,项目中的jsp
全部使用action做了包装,用户在地址栏看不到jsp
结尾的URL。
而B项目虽然使用了Spring+Struts的组合,但实际上仅仅使用了Struts2提供的国际化和s标签,代码中定义的Action仅用于转发请求至jsp
,用户在浏览器的地址栏里可以明确的看到当前页面的jsp
文件名和路径。
如下是A项目struts.xml文件中action后缀的配置
<constant name="struts.action.extension" value="action"/>
如下是B项目struts.xml文件中action后续的配置
<constant name="struts.action.extension" value="jsp,action"/>
问题在于A项目并没有遇到B项目现在遇到的问题。
分析到这里,尝试调整struts.xml的配置,去掉配置中的jsp
,如下所示
<constant name="struts.action.extension" value="action"/>
这样action后缀的配置和A项目保持一致。
重启应用之后,使用Google Chrome提供的调试面板,检查Web服务器对jsp
页面的响应,发现居然有HTTP安全头部。这说明,action后缀的配置对安全头部的生成有影响,但具体什么影响还未知,并且出于技术原因,目前并不能调整action后缀的配置。因此这问题还不算完,需要继续分析。
依据J2EE规范中Filter和Servlet的定义,我们知道Filter在执行时需要等待Servlet完成处理并写出响应后才会逐个返回,因此观察Servlet的运行栈,可以看到Web请求的处理路径。既然调整action后缀的配置对安全头部的生成有影响,那么说明不同的配置条件下,jsp的执行路径是有差异的,因此观察运行栈一定可以发现点什么。
但问题是对于代码里的Servlet类,可以使用eclipse的调试手段,在代码里打上断点,观察执行栈,但对于jsp来说,使用打断点来检查栈的方法就行不通了。那怎么办呢?
其实方法很简单,jsp页面内可以写Java代码,因此可以在页面上定义一个java.lang.Throwable
对象,然后使用该对象来输出当前调用栈。代码样例如下所示:
<% new Throwable().printStackTrace(); %>
于是调整action后缀的配置,使用浏览器访问页面,提取页面生成的栈。
如下是action后缀配置为jsp,action
时的栈。
java.lang.Throwable at org.apache.jsp.index_jsp._jspService(index_jsp.java:115) at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70) at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:438) at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:385) at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:329) at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:232) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:64) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:702) at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:450) at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:375) at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:302) at org.apache.struts2.dispatcher.ServletDispatcherResult.doExecute(ServletDispatcherResult.java:164) at org.apache.struts2.dispatcher.StrutsResultSupport.execute(StrutsResultSupport.java:191) at com.opensymphony.xwork2.DefaultActionInvocation.executeResult(DefaultActionInvocation.java:372) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:276) at org.apache.struts2.interceptor.DeprecationInterceptor.intercept(DeprecationInterceptor.java:41) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor.intercept(ConversionErrorInterceptor.java:138) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:229) at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:229) at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at org.apache.struts2.interceptor.MultiselectInterceptor.intercept(MultiselectInterceptor.java:73) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at org.apache.struts2.interceptor.DateTextFieldInterceptor.intercept(DateTextFieldInterceptor.java:125) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at org.apache.struts2.interceptor.CheckboxInterceptor.intercept(CheckboxInterceptor.java:91) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at com.opensymphony.xwork2.interceptor.PrepareInterceptor.doIntercept(PrepareInterceptor.java:171) at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at org.apache.struts2.interceptor.ServletConfigInterceptor.intercept(ServletConfigInterceptor.java:164) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor.intercept(ExceptionMappingInterceptor.java:189) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at org.apache.struts2.impl.StrutsActionProxy.execute(StrutsActionProxy.java:54) at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:567) at org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:81) at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:99) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:105) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:506) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:1078) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:757) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1520) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745)
如下是action后缀配置为action
时的栈。
java.lang.Throwable at org.apache.jsp.index_jsp._jspService(index_jsp.java:115) at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70) at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:438) at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:385) at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:329) at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:232) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.catalina.filters.HttpHeaderSecurityFilter.doFilter(HttpHeaderSecurityFilter.java:120) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:96) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:105) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:506) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:1078) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:757) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1520) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745)
对比之下,有如下发现:
-
当action后续为
jsp,action
时- 栈信息很长。
- 栈中出现了很多Struts2相关的栈帧(stack frame),说明页面访问请求被Struts2的过滤器拦截,符合预期。
- 栈中未出现
HttpHeaderSecurityFilter
相关的栈帧(stack frame)。 Log4jServletFilter
相关的栈帧(stack frame)出现了两次,为什么?
-
当前action后缀为
action
时- 栈信息很短。
- 栈中没有Struts2相关的栈帧(stack frame),说明页面访问请求没有被被Struts2的过滤器拦截,符合预期。
- 栈中出现了
HttpHeaderSecurityFilter
相关的栈帧(stack frame)。 Log4jServletFilter
相关的栈帧(stack frame)出现了一次,有点意思。
旧的问题没解决,新的问题又出现了。action后缀的配置,看来不单对HttpHeaderSecurityFilter
产生了影响,对Log4jServletFilter
的行为也有影响。
于是检查Log4jServletFilter
的配置,如下
<listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <!-- log4j2-begin --> <listener> <listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class> </listener> <filter> <filter-name>log4jServletFilter</filter-name> <filter-class>org.apache.logging.log4j.web.Log4jServletFilter</filter-class> </filter> <filter-mapping> <filter-name>log4jServletFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>ERROR</dispatcher> </filter-mapping>
咦,怎么filter-mapping
还可以配置dispatcher
,这是什么鬼?先不管它,参照Log4jServletFilter
的配置,修改HttpHeaderSecurityFilter
的配置信息。
<filter> <filter-name>httpHeaderSecurity</filter-name> <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class> <async-supported>true</async-supported> </filter> <filter-mapping> <filter-name>httpHeaderSecurity</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping>
重启应用之后使用浏览器的调试面板观察页面的响应数据,久违的HTTP安全头部终于出现了。
Content-Type:text/html;charset=UTF-8 Date:Tue, 24 May 2016 16:15:21 GMT Server:Apache-Coyote/1.1 Strict-Transport-Security:max-age=0 Transfer-Encoding:chunked X-Content-Type-Options:nosniff X-Frame-Options:DENY X-XSS-Protection:1; mode=block
这时,检查栈信息,可以看到HttpHeaderSecurityFilter
相关的栈帧(stack frame)。
java.lang.Throwable at org.apache.jsp.index_jsp._jspService(index_jsp.java:115) at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70) at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:438) at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:385) at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:329) at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:232) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.catalina.filters.HttpHeaderSecurityFilter.doFilter(HttpHeaderSecurityFilter.java:120) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:64) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:702) at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:450) at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:375) at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:302) at org.apache.struts2.dispatcher.ServletDispatcherResult.doExecute(ServletDispatcherResult.java:164) at org.apache.struts2.dispatcher.StrutsResultSupport.execute(StrutsResultSupport.java:191) at com.opensymphony.xwork2.DefaultActionInvocation.executeResult(DefaultActionInvocation.java:372) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:276) at org.apache.struts2.interceptor.DeprecationInterceptor.intercept(DeprecationInterceptor.java:41) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor.intercept(ConversionErrorInterceptor.java:138) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:229) at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:229) at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at org.apache.struts2.interceptor.MultiselectInterceptor.intercept(MultiselectInterceptor.java:73) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at org.apache.struts2.interceptor.DateTextFieldInterceptor.intercept(DateTextFieldInterceptor.java:125) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at org.apache.struts2.interceptor.CheckboxInterceptor.intercept(CheckboxInterceptor.java:91) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at com.opensymphony.xwork2.interceptor.PrepareInterceptor.doIntercept(PrepareInterceptor.java:171) at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at org.apache.struts2.interceptor.ServletConfigInterceptor.intercept(ServletConfigInterceptor.java:164) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor.intercept(ExceptionMappingInterceptor.java:189) at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:245) at org.apache.struts2.impl.StrutsActionProxy.execute(StrutsActionProxy.java:54) at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:567) at org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:81) at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:99) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:105) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:506) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:1078) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:757) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1520) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745)
定位结论
折腾这么久,终于把解决方法整出来了,其实很简单。
当前struts.xml中有如下配置
<constant name="struts.action.extension" value="action,jsp"/>
配置安全头部的过滤器时,需要在URL匹配模式上增加REQUEST
和FORWARD
。
<filter> <filter-name>httpHeaderSecurity</filter-name> <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class> <async-supported>true</async-supported> </filter> <filter-mapping> <filter-name>httpHeaderSecurity</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping>
原因应该和Struts2重定向请求至页面的方式相关,不过暂时没有时间去研究Struts2,期望后续会有所了解。
资料
关于dispatcher
的一些资料。