HTTP安全头部对jsp页面不生效

  • HTTP安全头部对jsp页面不生效已关闭评论
  • 18 次浏览
  • A+
所属分类:Web前端
摘要

本文于2016年4月底完成,发布在个人博客网站上。
考虑个人博客因某种原因无法修复,于是在博客园安家,之前发布的文章逐步搬迁过来。

本文于2016年4月底完成,发布在个人博客网站上。
考虑个人博客因某种原因无法修复,于是在博客园安家,之前发布的文章逐步搬迁过来。


诡异的问题

AppScan扫描报告中提示,Web服务器返回jscsspngjsp页面的HTTP响应中缺少安全头部。HTTP的安全头部包括HTTP Strict Transport SecurityX-Frame-OptionsX-Content-Type-OptionsX-XSS-ProtectionContent-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服务器返回jscsspng时,在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匹配模式上增加REQUESTFORWARD

<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的一些资料。