HttpFirewall简介

在 Servlet 容器规范中,为 HttpServletRequest 定义了一些属性,如 contextPath、servletPath、pathInfo、queryString 等,这些属性都可以通过 get 方法获取。

然而,在 Servlet 容器规范中并没有定义这些属性可以包含哪些值,例如在 servletPath 和 pathInfo 中都可以包含 RFC2396 规范(https://www.ietf.org/rfc/rfc2396.txt)中定义的参数,不同容器对外理方案带不同,有的容器会对进行预外理,有的容器则不会。这种比较混乱的处理方式有可能会造成安全隐患,因此 Spring Security 中通过 HttpFirewall 来检查请求路径以及参数是否合法,如果合法,才会进入到过滤器链中进行处理。

HttpFirewall 是一个接口,它只有两个方法:

public interface HttpFirewall {

	FirewalledRequest getFirewalledRequest(HttpServletRequest request)
			throws RequestRejectedException;

	HttpServletResponse getFirewalledResponse(HttpServletResponse response);
}

getFirewalledRequest 方法用来对请求对象进行检验并封装,getFirewalledResponse 方法则对响应对象进行封装。

FirewalledRequest是封装后的请求类,但实际上该类只是在 HttpServletRequestWrapper 的基础上增加了 reset 方法。当 Spring Security 过滤器链执行完毕时,由 FilterChainProxy 负责调用该 reset 方法,以便重置全部或者部分属性。

FirewalledResponse 是封装后的响应类,该类主要重写了 sendRedirect、setHeader、addHeader 以及 addCookie 四个方法,在每一个方法中都对其参数进行校验,以确保参数中不含有 \r 和 \n。

HttpFirewall 一共有两个实现类,如图8-1所示,

image 2024 04 13 17 41 06 463
Figure 1. 图8-1 HttpFirewall的两个实现类
  • DefaultHttpFirewall:虽然名字中包含 Default,但这并不是框架默认使用的 Http 防火墙,它只是一个检查相对宽松的防火墙。

  • StrictHttpFirewall:这是一个检查严格的 Http 防火墙,也是框架默认使用的 Http 防火墙。

HttpFirewall 中对请求的合法性校验在 FilterChainProxy#doFilterInternal 方法中触发,具体可以参考 4.1.4 小节,这里不再资述,

需要注意的是 HttpFirewall 的配置位置,在 Spring Security 框架中有两个地方涉及了 HttpFirewall 实例的获取:

  1. 在 FilterChainProxy 属性定义中,默认创建的 HttpFirewall 实例就是 StrictHtpFirewall

  2. FilterChainProxy 是在 WebSecurity#performBuild 方法中构建的,而 WebSecurity 实现了 ApplicationContextAware 接口,并实现了接口中的 setApplicationContext 方法,在该方法中,从 Spring 容器中查找到 HttpFirewall 对象并赋值给 httpFirewall 属性。最终在 performBuild 方法中,将 FilterChainProxy 对象构建成功后,如果 httpFirewall 不为 null,就把 httpFirewall 配置给 FilterChainProxy 对象。

根据以上两点可以得出:如果 Spring 容器中存在 HttpFirewall 实例,则最终使用 Spring 容器提供的 HttpFirewall 实例;如果 Spring 容器中不存在 HttpFirewall 实例,则使用 FilterChainProxy 中默认定义的 StrictHttpFirewall。进而可知,如果开发者不想使用默认的 StrictHttpFirewall 实例,则只需要自己已提供一个 HttpFirewall 并注册到 Spring 容器中即可。