13. 安全筛选器链

Spring Security 的 Web 基础结构完全基于标准 Servlet 过滤器。它在内部不使用 servlet 或任何其他基于 servlet 的框架(例如 Spring MVC),因此它与任何特定的 Web 技术都没有牢固的链接。它处理HttpServletRequestHttpServletResponse,并且不关心请求来自浏览器,Web 服务 Client 端,HttpInvoker还是 AJAX 应用程序。

Spring Security 在内部维护一个过滤器链,其中每个过滤器都有特定的责任,并且根据需要的服务将过滤器添加到配置中或从配置中删除。过滤器的 Sequences 很重要,因为它们之间存在依赖性。如果您一直在使用namespace configuration,那么将自动为您配置过滤器,而不必显式定义任何 Spring Bean,但是有时您可能希望完全控制安全过滤器链,这可能是因为您使用的功能命名空间中不受支持,或者您正在使用自己的自定义版本的类。

13.1 DelegatingFilterProxy

使用 servlet 过滤器时,显然需要在web.xml中声明它们,否则 servlet 容器将忽略它们。在 Spring Security 中,过滤器类也是在应用程序上下文中定义的 Spring Bean,因此能够利用 Spring 丰富的依赖注入机制和生命周期接口。 Spring 的DelegatingFilterProxy提供web.xml和应用程序上下文之间的链接。

使用DelegatingFilterProxy时,您将在web.xml文件中看到以下内容:

<filter>
<filter-name>myFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

请注意,过滤器实际上是DelegatingFilterProxy,而不是实际上将实现过滤器逻辑的类。 DelegatingFilterProxy的工作是将Filter的方法委托给从 Spring 应用程序上下文获得的 bean。这使 Bean 可以从 Spring Web 应用程序上下文生命周期支持和配置灵 Active 中受益。 Bean 必须实现javax.servlet.Filter,并且必须与filter-name元素中的名称相同。阅读DelegatingFilterProxy的 Javadoc 了解更多信息

13.2 FilterChainProxy

Spring Security 的 Web 基础结构只能通过委派给FilterChainProxy的实例来使用。安全过滤器不应自己使用。从理论上讲,您可以在应用程序上下文文件中声明所需的每个 Spring Security 过滤器 bean,并为每个过滤器在web.xml上添加一个对应的DelegatingFilterProxy条目,以确保正确排序,但这会很麻烦并且会使web.xml文件杂乱无章如果您有很多过滤器,则速度很快。 FilterChainProxy让我们向web.xml添加一个条目,并完全处理用于 Management Web 安全 bean 的应用程序上下文文件。就像上面的示例一样,它使用DelegatingFilterProxy进行接线,但是将filter-name设置为 bean 名称“ filterChainProxy”。然后在应用程序上下文中使用相同的 bean 名称声明过滤器链。这是一个例子:

<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<constructor-arg>
	<list>
	<sec:filter-chain pattern="/restful/**" filters="
		securityContextPersistenceFilterWithASCFalse,
		basicAuthenticationFilter,
		exceptionTranslationFilter,
		filterSecurityInterceptor" />
	<sec:filter-chain pattern="/**" filters="
		securityContextPersistenceFilterWithASCTrue,
		formLoginFilter,
		exceptionTranslationFilter,
		filterSecurityInterceptor" />
	</list>
</constructor-arg>
</bean>

为了方便起见,使用命名空间元素filter-chain来设置应用程序内所需的安全过滤器链。 [6]。它将特定的 URL 模式 Map 到由filters元素中指定的 bean 名称构建的过滤器列表,并将它们组合为SecurityFilterChain类型的 bean。 pattern属性采用“Ant 路径”,最具体的 URI 应该首先出现在[7]上。在运行时FilterChainProxy将找到与当前 Web 请求匹配的第一个 URI 模式,并且filters属性指定的过滤器 bean 列表将应用于该请求。筛选器将按照定义的 Sequences 调用,因此您可以完全控制应用于特定 URL 的筛选器链。

您可能已经注意到,我们在过滤器链中声明了两个SecurityContextPersistenceFilter(ASCallowSessionCreation的缩写,SecurityContextPersistenceFilter的属性)。由于 Web 服务永远不会在将来的请求中提供jsessionid,因此为此类用户代理创建HttpSession会很浪费。如果您有需要最大可伸缩性的大容量应用程序,则建议您使用上面显示的方法。对于较小的应用程序,使用单个SecurityContextPersistenceFilter(其默认allowSessionCreation作为true)可能就足够了。

请注意,FilterChainProxy不会在配置它的过滤器上调用标准过滤器生命周期方法。我们建议您像其他任何 Spring bean 一样,使用 Spring 的应用程序上下文生命周期接口作为替代。

当我们研究如何使用namespace configuration设置网络安全性时,我们使用了名称为“ springSecurityFilterChain”的DelegatingFilterProxy。现在,您应该能够看到这是名称空间创建的FilterChainProxy的名称。

13.2.1 绕过过滤器链

您可以使用属性filters = "none"代替提供过滤器 bean 列表。这将完全省略来自安全过滤器链的请求模式。请注意,与此路径匹配的所有内容都将不应用身份验证或授权服务,并且可以自由访问。如果要在请求期间使用SecurityContext内容的内容,则它必须已通过安全过滤器链。否则,将不会填充SecurityContextHolder,并且内容将为 null。

13.3 过滤器 Order

链中定义过滤器的 Sequences 非常重要。无论您实际使用哪个过滤器,其 Sequences 都应如下:

  • ChannelProcessingFilter,因为它可能需要重定向到其他协议

  • SecurityContextPersistenceFilter,因此可以在网络请求开始时在SecurityContextHolder中设置SecurityContext,并且在网络请求结束时(对下一个网络请求可用),对SecurityContext所做的任何更改都可以复制到HttpSession

  • ConcurrentSessionFilter,因为它使用SecurityContextHolder功能并且需要更新SessionRegistry以反映来自委托人的持续请求

  • 认证处理机制UsernamePasswordAuthenticationFilterCasAuthenticationFilterBasicAuthenticationFilter等-以便可以将SecurityContextHolder修改为包含有效的Authentication请求令牌

  • SecurityContextHolderAwareRequestFilter,如果您使用它来将支持 Spring Security 的HttpServletRequestWrapper安装到 servlet 容器中

  • JaasApiIntegrationFilter,如果SecurityContextHolder中有JaasAuthenticationToken,则它将FilterChain作为JaasAuthenticationToken中的Subject处理。

  • RememberMeAuthenticationFilter,因此,如果没有较早的身份验证处理机制更新SecurityContextHolder,并且该请求提出一个 cookie 来启用“记住我”服务,则会在此处放置一个合适的记住Authentication对象

  • AnonymousAuthenticationFilter,因此,如果没有较早的身份验证处理机制更新SecurityContextHolder,则将在其中放置一个匿名Authentication对象

  • ExceptionTranslationFilter,以捕获任何 Spring Security 异常,以便可以返回 HTTP 错误响应或启动适当的AuthenticationEntryPoint

  • FilterSecurityInterceptor,以保护 Web URI 并在拒绝访问时引发异常

13.4 请求匹配和 HttpFirewall

Spring Security 在多个区域中针对传入的请求对您定义的模式进行测试,以便确定应如何处理请求。当FilterChainProxy决定请求应通过哪个过滤器链时,以及FilterSecurityInterceptor决定哪些安全性约束适用于请求时,就会发生这种情况。在针对您定义的模式进行测试时,了解该机制是什么以及使用哪个 URL 值很重要。

Servlet 规范为HttpServletRequest定义了几个属性,可通过 getter 方法访问这些属性,我们可能希望将其匹配。这些是contextPathservletPathpathInfoqueryString。 Spring Security 只对保护应用程序中的路径感兴趣,因此contextPath被忽略。不幸的是,该 servlet 规范没有确切定义servletPathpathInfo的值将包含特定请求 URI 的内容。例如,URL 的每个路径段都可以包含RFC 2396 [8]中定义的参数。规范没有明确规定是否将这些值包含在servletPathpathInfo值中,并且行为在不同的 servlet 容器之间有所不同。存在以下危险:当将应用程序部署在不从这些值中剥离路径参数的容器中时,攻击者可能会将其添加到请求的 URL 中,从而导致模式匹配意外成功或失败。 [9]。传入 URL 中的其他变体也是可能的。例如,它可能包含路径遍历序列(如/../)或多个正斜杠(//),这也可能导致模式匹配失败。一些容器在执行 servlet Map 之前将它们标准化,但其他容器则没有。为了防止此类问题,FilterChainProxy使用HttpFirewall策略检查并包装请求。默认情况下,未规范的请求将自动被拒绝,并且出于匹配目的,将删除路径参数和重复的斜杠。 [10]。因此,必须使用FilterChainProxy来 Management 安全过滤器链。请注意servletPathpathInfo值是由容器解码的,因此您的应用程序不应具有任何包含分号的有效路径,因为出于匹配目的将删除这些部分。

如上所述,默认策略是使用 Ant 样式的路径进行匹配,这对于大多数用户而言可能是最佳选择。该策略在类AntPathRequestMatcher中实现,该类使用 Spring 的AntPathMatcher对连接的servletPathpathInfo执行模式的大小写不敏感匹配,而忽略queryString

如果由于某种原因需要更强大的匹配策略,则可以使用正则表达式。该策略的实现为RegexRequestMatcher。有关更多信息,请参见 Javadoc。

实际上,我们建议您在服务层使用方法安全性来控制对应用程序的访问,并且不要完全依赖于在 Web 应用程序级别定义的安全性约束的使用。 URL 会发生变化,很难考虑到应用程序可能支持的所有可能的 URL 以及如何处理请求。您应该尝试限制自己使用一些简单易懂的简单 Ant 路径。始终尝试使用“默认拒绝”方法,在此方法中,您最后定义了一个全包通配符(/ )并拒绝访问。

在服务层定义的安全性更健壮,更难绕开,因此您应始终利用 Spring Security 的方法安全性选项。

HttpFirewall还通过拒绝 HTTP 响应 Headers 中的换行符来防止HTTP 响应拆分

默认情况下使用StrictHttpFirewall。此实现拒绝看起来是恶意的请求。如果对您的要求过于严格,则可以自定义拒绝哪些类型的请求。但是,重要的是要知道这样做可以使您的应用程序容易受到攻击。例如,如果您希望利用 Spring MVC 的矩阵变量,可以在 XML 中使用以下配置:

<b:bean id="httpFirewall"
      class="org.springframework.security.web.firewall.StrictHttpFirewall"
      p:allowSemicolon="true"/>

<http-firewall ref="httpFirewall"/>

使用 Java 配置,可以通过公开StrictHttpFirewall bean 实现相同的目的。

@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowSemicolon(true);
    return firewall;
}

13.5 与其他基于过滤器的框架一起使用

如果您使用的是基于过滤器的其他框架,则需要确保首先使用 Spring Security 过滤器。这样可以及时填充SecurityContextHolder,以供其他过滤器使用。例如,使用 SiteMesh 装饰网页或使用过滤器处理其请求的 Wicket 之类的网络框架。

13.6 高级命名空间配置

正如我们在名称空间一章的前面所看到的,可以使用多个http元素为不同的 URL 模式定义不同的安全配置。每个元素都会在内部FilterChainProxy和应该 Map 到其的 URL 模式内创建一个过滤器链。元素将按照声明的 Sequences 添加,因此最具体的模式必须再次声明。这是另一个示例,与上述情况类似,该应用程序既支持 StatelessRESTful API,也支持用户使用表单登录的普通 Web 应用程序。

<!-- Stateless RESTful service using Basic authentication -->
<http pattern="/restful/**" create-session="stateless">
<intercept-url pattern='/**' access="hasRole('REMOTE')" />
<http-basic />
</http>

<!-- Empty filter chain for the login page -->
<http pattern="/login.htm*" security="none"/>

<!-- Additional filter chain for normal users, matching all other requests -->
<http>
<intercept-url pattern='/**' access="hasRole('USER')" />
<form-login login-page='/login.htm' default-target-url="/home.htm"/>
<logout />
</http>

[6]请注意,您需要在应用程序上下文 XML 文件中包括安全名称空间,才能使用此语法。仍支持使用filter-chain-map的旧语法,但不推荐使用,而推荐使用构造函数自变量注入。

[7]代替路径模式,可以使用request-matcher-ref属性指定RequestMatcher实例,以实现更强大的匹配

[8]当浏览器不支持 cookie 且将jsessionid参数附加到分号后的 URL 时,您可能已经看到了。但是,RFC 允许这些参数出现在 URL 的任何路径段中

[9]一旦请求离开FilterChainProxy,将返回原始值,因此应用程序仍然可以使用它们。

[10]因此,例如,原始请求路径/secure;hack=1/somefile.html;hack=2将返回为/secure/somefile.html