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


    本文于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>httpHeaderSecurityfilter-name>
    <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilterfilter-class>
    <async-supported>trueasync-supported>
    filter>
    <filter-mapping>
    <filter-name>httpHeaderSecurityfilter-name>
    <url-pattern>/*url-pattern>
    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>struts2filter-name>
    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilterfilter-class>
    filter>
    <filter-mapping>
    <filter-name>struts2filter-name>
    <url-pattern>/*url-pattern>
    filter-mapping>

    简化后的struts.xml配置文件,内容如下:

    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.jspresult>
    global-results>
    <global-exception-mappings>
    <exception-mapping exception="java.lang.Exception" result="error"/>
    global-exception-mappings>
    <action name="*" class="MainAction">
    <result name="success">{1}.jspresult>
    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>httpHeaderSecurityfilter-name>
    <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilterfilter-class>
    <async-supported>trueasync-supported>
    filter>
    <filter-mapping>
    <filter-name>httpHeaderSecurityfilter-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.Log4jConfigListenerlistener-class>
    listener>
    <listener>
    <listener-class>org.apache.logging.log4j.web.Log4jServletContextListenerlistener-class>
    listener>
    <filter>
    <filter-name>log4jServletFilterfilter-name>
    <filter-class>org.apache.logging.log4j.web.Log4jServletFilterfilter-class>
    filter>
    <filter-mapping>
    <filter-name>log4jServletFilterfilter-name>
    <url-pattern>/*url-pattern>
    <dispatcher>REQUESTdispatcher>
    <dispatcher>FORWARDdispatcher>
    <dispatcher>INCLUDEdispatcher>
    <dispatcher>ERRORdispatcher>
    filter-mapping>

    咦,怎么filter-mapping还可以配置dispatcher,这是什么鬼?先不管它,参照Log4jServletFilter的配置,修改HttpHeaderSecurityFilter的配置信息。

    <filter>
    <filter-name>httpHeaderSecurityfilter-name>
    <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilterfilter-class>
    <async-supported>trueasync-supported>
    filter>
    <filter-mapping>
    <filter-name>httpHeaderSecurityfilter-name>
    <url-pattern>/*url-pattern>
    <dispatcher>REQUESTdispatcher>
    <dispatcher>FORWARDdispatcher>
    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>httpHeaderSecurityfilter-name>
    <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilterfilter-class>
    <async-supported>trueasync-supported>
    filter>
    <filter-mapping>
    <filter-name>httpHeaderSecurityfilter-name>
    <url-pattern>/*url-pattern>
    <dispatcher>REQUESTdispatcher>
    <dispatcher>FORWARDdispatcher>
    filter-mapping>

    原因应该和Struts2重定向请求至页面的方式相关,不过暂时没有时间去研究Struts2,期望后续会有所了解。

    资料

    关于dispatcher的一些资料。

  • 相关阅读:
    CSS选择器练习小游戏
    VC++控制台程序隐藏窗口运行
    MySQL下载安装配置方法
    Nginx服务配置及相关模块
    Dijkstra算法——单源最短路径查找
    Java 动态判断数组维数并取值
    代码随想录阅读笔记-字符串【翻转字符串中单词】
    无服务器学习02:挑战及发展
    高并发技巧-流量聚合和高并发写入处理技巧
    SSM学生惩奖系统的设计与实现毕业设计-附源码201520
  • 原文地址:https://www.cnblogs.com/jackieathome/p/17955605