13. The Security Filter Chain(安全过滤链)
Spring Security的网络基础设施完全基于标准的servlet过滤器。它在内部不使用servlet或任何其他基于servlet的框架(如Spring MVC),因此它与任何特定的web技术都没有强有力的链接。它处理HttpServletRequest和HttpServletResponse,不关心请求是来自浏览器、网络服务客户端、HttpInvoker还是AJAX应用程序。
Spring Security在内部维护一个过滤器链,其中每个过滤器都有特定的职责,并且根据需要的服务从配置中添加或删除过滤器。过滤器的排序很重要,因为它们之间存在依赖关系。如果您一直在使用命名空间配置,那么过滤器会自动为您配置,并且您不必显式定义任何Spring beans,但是有时您可能希望完全控制安全过滤器链,这可能是因为您正在使用命名空间中不支持的功能,或者您正在使用您自己定制的类版本。
13.1 DelegatingFilterProxy(委托过滤器代理)
当使用servlet过滤器时,您显然需要在您的web.xml中声明它们,否则它们将被servlet容器忽略。在Spring Security中,过滤器类也是在应用程序上下文中定义的Spring beans,因此能够利用Spring丰富的依赖注入工具和生命周期接口。Spring的DelegatingFilterProxy提供了web.xml和应用程序上下文之间的链接。
当使用DelegatingFilterProxy时,您将在web.xml文件中看到类似的内容:
请注意,过滤器实际上是一个DelegatingFilterProxy,而不是实际实现过滤器逻辑的类。DelegatingFilterProxy所做的是将过滤器的方法委托给一个从Spring应用程序上下文中获得的bean。这使bean能够受益于Spring web应用程序上下文生命周期支持和配置灵活性。该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添加一个条目,并完全处理应用程序上下文文件来管理我们的web安全beans。它使用一个DelegatingFilterProxy进行连接,就像上面的例子一样,但是过滤器名称被设置为bean名称“filterChainProxy”。然后在应用程序上下文中用相同的bean名称声明过滤器链。这里有一个例子:
命名空间元素filter-chain用于方便地设置应用程序中所需的安全过滤器链。它将一个特定的URL模式映射到一个由filters元素中指定的bean名称构建的筛选器列表,并将它们组合到一个SecurityFilterChain类型的bean中。模式属性采用ant路径,最具体的URIs应该首先出现,在运行时,FilterChainProxy将定位与当前网络请求匹配的第一个URI模式,并且由过滤器属性指定的过滤器beans列表将应用于该请求。过滤器将按照它们被定义的顺序被调用,因此您可以完全控制应用于特定网址的过滤器链。
您可能已经注意到我们在筛选器链中声明了两个securitycontextPersistenceFilter(ASC是allowSessionCreation的缩写,是securitycontextPersistenceFilter的一个属性)。因为web服务永远不会在将来的请求中提供jsessionid,所以为这样的用户代理创建HttpSession是一种浪费。如果您有一个需要最大可伸缩性的大容量应用程序,我们建议您使用上面显示的方法。对于较小的应用,使用单个securityContextPersistenceFilter(其默认的allowSessionCreation为true)可能就足够了。
请注意,FilterChainProxy不会在其配置的过滤器上调用标准过滤器生命周期方法。我们建议您使用Spring的应用程序上下文生命周期接口作为替代,就像您使用任何其他Spring bean一样。
当我们研究如何使用命名空间配置来设置网络安全时,我们使用了一个名为“springSecurityFilterChain”的DelegatingFilterProxy。您现在应该能够看到这是由命名空间创建的FilterChainProxy的名称。
13.2.1 Bypassing the Filter Chain(旁路过滤器链)
您可以使用属性过滤器=“none”作为提供过滤器bean列表的替代方法。这将从安全过滤器链中完全省略请求模式。请注意,匹配此路径的任何内容都不会应用任何身份验证或授权服务,并且可以自由访问。如果您想在请求期间使用安全上下文的内容,那么它必须已经通过了安全过滤器链。否则,SecurityContextHolder将不会被填充,并且内容将为空。
13.3 Filter Ordering(过滤器排序)
过滤器在链中的定义顺序非常重要。不管您实际使用的是哪种过滤器,顺序都应该如下:
通道处理过滤器(ChannelProcessingFilter),因为它可能需要重定向到不同的协议。
securitycontextPersistenceFilter,这样就可以在web请求开始时在SecurityContextHolder中设置一个SecurityContext,并且当web请求结束时,对SecurityContext的任何更改都可以复制到HttpSession中(准备用于下一个web请求)。
ConcurrentSessionFilter,因为它使用SecurityContextHolder功能,并且需要更新会话注册以反映主体的持续请求。
身份验证处理机制(Authentication processing mechanisms)-用户名密码身份验证过滤器(UsernamePasswordAuthenticationFilter)、身份验证过滤器(CasAuthenticationFilter)、基本身份验证过滤器(BasicAuthenticationFilter )等-以便可以修改安全上下文持有者(SecurityContextHolder )以包含有效的身份验证请求令牌。
securitycontextholderAreRequestFilter,如果您正在使用它在您的servlet容器中安装一个支持Spring安全性的HttpServletRequestWrapper。
JaasApiIntegrationFilter,如果JaasAuthenticationToken在SecurityContextHolder中,这将把FilterChain作为JaasAuthenticationToken中的主题进行处理。
RememberMeAuthenticationFilter,如果没有更早的身份验证处理机制更新了SecurityContextHolder,并且请求提供了一个cookie,使得remember-me的服务能够发生,那么合适的记住的身份验证对象将被放在那里。
异常转换过滤器(ExceptionTranslationFilter),捕获任何Spring安全异常,以便返回一个HTTP错误响应或启动一个适当的AuthenticationEntryPoint。
过滤器安全接口(FilterSecurityInterceptor),用于保护网络URIs,并在访问被拒绝时引发异常。
13.4 Request Matching and HttpFirewall(请求匹配和HttpFirewall)
Spring Security有几个方面,您定义的模式会根据传入的请求进行测试,以便决定应该如何处理请求。当FilterChainProxy决定一个请求应该通过哪个过滤器链时,以及当FilterSecurityInterceptor决定哪个安全约束应用于一个请求时,就会发生这种情况。根据您定义的模式进行测试时,了解机制是什么以及使用什么URL值是很重要的。
Servlet规范为HttpServletRequest定义了几个属性,这些属性可以通过getter方法来访问,我们可能希望与之匹配。这些是contextPath、servletPath、pathInfo 和queryString。Spring Security只对保护应用程序中的路径感兴趣,因此忽略了上下文路径。不幸的是,servlet规范没有明确定义servletPath和pathInfo的值对于特定的请求URI将包含什么。例如,如RFC 2396 [8]中所定义的,一个网址的每个路径段可以包含参数。规范没有明确说明这些是否应该包含在servletPath和pathInfo值中,不同的servlet容器之间的行为也不同。当应用程序部署在没有从这些值中剥离路径参数的容器中时,存在一种危险,攻击者可能会将它们添加到请求的URL中,以导致模式匹配意外成功或失败。传入网址的其他变化也是可能的。例如,它可以包含路径遍历序列(如/../)或多个正斜杠(//),这也可能导致模式匹配失败。一些容器在执行servlet映射之前将它们规范化,但是其他的没有。为了防止类似这样的问题,FilterChainProxy使用HttpFirewall策略来检查和包装请求。默认情况下,未规范化的请求会被自动拒绝,并且出于匹配目的,路径参数和重复斜线会被删除。因此,使用过滤器链代理来管理安全过滤器链是非常重要的。请注意,servletPath和pathInfo值是由容器解码的,因此您的应用程序不应该有任何包含分号的有效路径,因为这些部分将被删除以进行匹配。
如上所述,默认策略是使用Ant风格的路径进行匹配,这可能是大多数用户的最佳选择。该策略在类AntPathRequestMatcher中实现,该类使用Spring的AntPathMatcher对连接的servletPath和pathInfo执行不区分大小写的模式匹配,忽略查询字符串。
如果出于某种原因,您需要更强大的匹配策略,您可以使用正则表达式。然后,策略实现是RegexRequestMatcher。有关更多信息,请参见该类的Javadoc。
在实践中,我们建议您在服务层使用方法安全性来控制对应用程序的访问,并且不要完全依赖于使用在web应用程序级别定义的安全性约束。网址会发生变化,很难考虑应用程序可能支持的所有可能的网址以及如何处理请求。你应该试着限制自己使用一些简单易懂的ant路径。始终尝试使用“deny-by-default”方法,在该方法中,您最后定义了一个总括通配符(/或)并拒绝访问。
在服务层定义的安全性更健壮,更难绕过,所以您应该总是利用Spring Security的方法安全性选项。
HTTP防火墙还通过拒绝HTTP响应头中的换行字符来防止HTTP响应拆分。
认情况下,使用StrictHttpFirewall。该实现拒绝看似恶意的请求。如果它对你的需求来说太严格,那么你可以定制什么类型的请求被拒绝。然而,重要的是你要知道这可能会使你的应用程序受到攻击。例如,如果您希望利用Spring MVC的矩阵变量,可以在XML中使用以下配置:
同样的事情也可以通过Java配置来实现,方法是公开一个StrictHttpFirewall bean。
13.5 Use with other Filter-Based Frameworks(与其他基于过滤器的框架一起使用)
如果您正在使用其他一些基于过滤器的框架,那么您需要确保首先使用Spring Security过滤器。这使得SecurityContextHolder能够被及时填充,以供其他过滤器使用。例子是使用SiteMesh来装饰你的网页或者像Wicket这样的网络框架,它使用过滤器来处理它的请求。
13.6 Advanced Namespace Configuration(高级命名空间配置)
正如我们在前面的命名空间章节中看到的,可以使用多个http元素为不同的URL模式定义不同的安全配置。每个元素在内部过滤器链代理中创建一个过滤器链,以及应该映射到它的网址模式。元素将按照它们被声明的顺序被添加,所以最具体的模式必须首先被声明。这是另一个例子,类似于上面的情况,应用程序既支持无状态RESTful API,也支持用户使用表单登录的普通web应用程序。
请注意,为了使用这种语法,您需要在应用程序上下文XML文件中包含安全命名空间。使用过滤器链映射的旧语法仍然受支持,但不赞成使用构造函数参数注入。
可以使用请求匹配器引用属性来指定一个请求匹配器实例,以实现更强大的匹配,而不是路径模式。
当浏览器不支持cookies,并且jsessionid参数被附加到分号后的URL时,您可能已经看到了这一点。然而,RFC允许在URL的任何路径段中存在这些参数。
一旦请求离开FilterChainProxy,将返回原始值,因此应用程序仍然可以使用原始值。
因此,例如,原始请求路径/secure;hack = 1/somefile . html;hack=2将作为/secure/somefile.html返回。