On this page
13. 安全筛选器链
Spring Security 的 Web 基础结构完全基于标准 Servlet 过滤器。它在内部不使用 servlet 或任何其他基于 servlet 的框架(例如 Spring MVC),因此它与任何特定的 Web 技术都没有牢固的链接。它处理HttpServletRequest
和HttpServletResponse
,并且不关心请求来自浏览器,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
(ASC
是allowSessionCreation
的缩写,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
以反映来自委托人的持续请求认证处理机制
UsernamePasswordAuthenticationFilter
,CasAuthenticationFilter
,BasicAuthenticationFilter
等-以便可以将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 方法访问这些属性,我们可能希望将其匹配。这些是contextPath
,servletPath
,pathInfo
和queryString
。 Spring Security 只对保护应用程序中的路径感兴趣,因此contextPath
被忽略。不幸的是,该 servlet 规范没有确切定义servletPath
和pathInfo
的值将包含特定请求 URI 的内容。例如,URL 的每个路径段都可以包含RFC 2396 [8]中定义的参数。规范没有明确规定是否将这些值包含在servletPath
和pathInfo
值中,并且行为在不同的 servlet 容器之间有所不同。存在以下危险:当将应用程序部署在不从这些值中剥离路径参数的容器中时,攻击者可能会将其添加到请求的 URL 中,从而导致模式匹配意外成功或失败。 [9]。传入 URL 中的其他变体也是可能的。例如,它可能包含路径遍历序列(如/../
)或多个正斜杠(//
),这也可能导致模式匹配失败。一些容器在执行 servlet Map 之前将它们标准化,但其他容器则没有。为了防止此类问题,FilterChainProxy
使用HttpFirewall
策略检查并包装请求。默认情况下,未规范的请求将自动被拒绝,并且出于匹配目的,将删除路径参数和重复的斜杠。 [10]。因此,必须使用FilterChainProxy
来 Management 安全过滤器链。请注意servletPath
和pathInfo
值是由容器解码的,因此您的应用程序不应具有任何包含分号的有效路径,因为出于匹配目的将删除这些部分。
如上所述,默认策略是使用 Ant 样式的路径进行匹配,这对于大多数用户而言可能是最佳选择。该策略在类AntPathRequestMatcher
中实现,该类使用 Spring 的AntPathMatcher
对连接的servletPath
和pathInfo
执行模式的大小写不敏感匹配,而忽略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
。