Spring Security 过滤器
Spring Security 的 Web 基础结构完全基于标准的 servlet 过滤器。Spring Security 在内部维护一个过滤器链,其中每个过滤器都有特定的责任,过滤器的顺序很重要,因为它们之间存在依赖关系。
过滤器链
-
DelegatingFilterProxy
使用 servlet 过滤器时,显然需要在
web.xml
中声明它们,否则 servlet 容器将忽略它们。在 Spring Security 中,过滤器类也是在应用程序上下文中定义的 Spring bean,因此能够利用 Spring 丰富的依赖注入工具和生命周期接口。Spring 的DelegatingFilterProxy
提供了web.xml
和应用程序上下文之间的链接。1
2
3
4
5
6
7
8
9<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 必须实现javax.servlet.Filter
,它必须与filter-name
元素中的名称相同。
DelegatingFilterProxy
继承GenericFilterBean
,该抽象类实现Filter
接口,并提供 Spring 的管理。但是委托类自己不去实现安全过滤,而是将过滤方法委托给FilterChainProxy
代理类去做。
1 |
|
这里的 delegateToUse
就是 FilterChainProxy
,代理类调用自己的 Filter
实现。
-
FilterChainProxy
Spring Security 的 Web 基础结构只能通过委托 FilterChainProxy 实例来实现。安全过滤器不应该使用自身。FilterChainProxy 允许我们向 web.xml 添加一个条目,并完全处理应用程序上下文文件以管理我们的 Web 安全 bean。
该类同样继承自
GenericFilterBean
,Filter
实现如下:
1 |
|
这里的 firewall 就实现了安全字符过滤,Url编码解码配置,访问方法配置等等安全策略。
真正执行安全过滤的是在其内部类 VirtualFilterChain
中,在该类中依次调用各个安全过滤器。
后处理配置实体 - ObjectPostProcessor
Spring Security 的 Java 配置不会公开它配置的每个对象的每个属性。这简化了大多数用户的配置。毕竟,如果每个属性都被暴露,用户可以使用标准 bean 配置。
虽然有充分的理由不直接公开每个属性,但用户可能仍需要更高级的配置选项。为了解决这个问题,Spring Security 引入了 ObjectPostProcessor
的概念,可用于修改或替换 Java Configuration 创建的许多 Object 实例。例如,如果要在 FilterSecurityInterceptor 上配置 filterSecurityPublishAuthorizationSuccess 属性,可以使用以下命令:
1 |
|
过滤器顺序
过滤器在链中定义的顺序非常重要:
ChannelProcessingFilter
:因为它可能需要重定向到不同的协议SecurityContextPersistenceFilter
: 因此,可以在 Web 请求开始时在 SecurityContextHolder 中设置 SecurityContext,并且当 Web 请求结束时(可以使用下一个 Web 请求准备好),可以将对 SecurityContext 的任何更改复制到 HttpSession。ConcurrentSessionFilter
: 因为它需要使用 SecurityContextHolder 的功能,而且更新对应 session 的最后更新时间,以及通过 SessionRegistry 获取当前的 SessionInformation 以检查当前的 session 是否已经过期,过期则会调用 LogoutHandler。- 身份验证处理机制 -
UsernamePasswordAuthenticationFilter
,CasAuthenticationFilter
,BasicAuthenticationFilter
等 - 以便可以修改 SecurityContextHolder 以包含有效的身份验证请求令牌。 SecurityContextHolderAwareRequestFilter
:使用它将 Spring Security 感知 HttpServletRequestWrapper 安装到您的 servlet 容器中。JaasApiIntegrationFilter
:如果 JaasAuthenticationToken 位于SecurityContextHolder 中,则会将 FilterChain 作为 JaasAuthenticationToken 中的 Subject 进行处理。RememberMeAuthenticationFilter
: 如果没有更早的身份验证处理机制更新 SecurityContextHolder,并且该请求提供了一个 cookie,使我能够记住我的服务,一个合适的 remembered Authentication 验证对象将会设给 SecurityContextHolder。AnonymousAuthenticationFilter
,这样如果没有早期的身份验证处理机制更新 SecurityContextHolder,那么该安全上下文将被匿名身份验证对象填充。ExceptionTranslationFilter
,用于捕获任何 Spring Security 异常,以便可以返回 HTTP 错误响应或启动相应的 AuthenticationEntryPoint。FilterSecurityInterceptor
,用于保护 Web URI 并在访问被拒绝时引发异常。
核心过滤器
-
FilterSecurityInterceptor
该过滤器负责处理 HTTP 资源的安全性,它需要一个 AuthenticationManager
和 AccessDecisionManager
的引用。它还提供了适用于不同 HTTP URL 请求的配置属性。
FilterSecurityInterceptor
可以通过两种方式配置配置属性。第一种,是使用命名空间元素 SecurityMetadataSource
,无论使用何种方法。SecurityMetadataSource 负责返回 List
应该注意的是,FilterSecurityInterceptor.setSecurityMetadataSource() 方法实际上需要 FilterInvocationSecurityMetadataSource 的实例。它是一个标记接口,表示它是 SecurityMetadataSource
的子类。它只是表示 SecurityMetadataSource 了解 FilterInvocation。为了简单起见,我们将继续将 FilterInvocationSecurityMetadataSource 称为 SecurityMetadataSource,因为这种区别与大多数用户没什么关系。
由命名空间语法创建的 SecurityMetadataSource 通过将请求 URL 与配置的 pattern 属性相匹配来获取特定 FilterInvocation 的配置属性。这与命名空间配置的行为方式相同。缺省情况是将所有表达式视为 Apache Ant 路径,并且对于更复杂的情况也支持正则表达式。request-matcher 属性用于指定正在使用的模式的类型。在同一定义中无法混合表达式语法。
始终按照定义的顺序评估模式。因此,在列表中定义的更具体的模式比不太具体的模式更高这一点很重要。
-
ExceptionTranslationFilter
ExceptionTranslationFilter
位于安全过滤器堆栈中的FilterSecurityInterceptor
之上。它不执行任何实际的安全实施,但处理安全拦截器抛出的异常并提供合适的 HTTP 响应。
1 | <bean id="exceptionTranslationFilter" |
-
AuthenticationEntryPoint
用户未进行身份验证时请求安全的 HTTP 资源时,会调用 AuthenticationEntryPoint
。安全拦截器将在调用堆栈的下方抛出适当的 AuthenticationException
或 AccessDeniedException
,触发入口点的 commence
方法。这样做的目的是向用户提供适当的响应,以便开始身份验证。我们在这里使用的是 LoginUrlAuthenticationEntryPoint
,它将请求重定向到不同的URL(通常是登录页面)。使用的实际实现将取决于您希望在应用程序中使用的身份验证机制。
-
如果抛出AccessDeniedHandler
AccessDeniedException
并且用户已经过身份验证,则这意味着此操作没有足够权限。在这种情况下,ExceptionTranslationFilter
将调用第二个策略AccessDeniedHandler
。默认情况下,使用AccessDeniedHandlerImpl
,它只向客户端发送 403(Forbidden)响应。你也可以实现自己的处理。 -
SavedRequest
和RequestCache
接口
ExceptionTranslationFilter
职责的另一个职责是在调用 AuthenticationEntryPoint
之前保存当前请求。这允许在用户进行身份验证后恢复请求,一个典型的例子是用户使用表单登录,然后通过默认的 SavedRequestAwareAuthenticationSuccessHandler
重定向到原始 URL。
RequestCache
封装了存储和检索 HttpServletRequest
实例所需的功能。默认使用 HttpSessionRequestCache
,它将请求存储在 HttpSession
中。当用户被重定向到原始 URL 时,RequestCacheFilter
的作用是实际从缓存中恢复已保存的请求。
-
SecurityContextPersistenceFilter
根据应用程序的类型,可能需要采用策略来在用户操作之间存储安全上下文。在典型的Web应用程序中,用户登录一次,然后由其 session Id 标识。服务器在会话期间缓存主体信息。在 Spring Security 中,在请求之间存储
SecurityContext
的责任属于SecurityContextPersistenceFilter
,它默认将上下文存储为HTTP请求之间的HttpSession
属性。它为每个请求恢复SecurityContextHolder
的上下文,并且至关重要的是,在请求完成时清除SecurityContextHolder
。出于安全目的,您不应直接与HttpSession
交互,使用 SecurityContextHolder 即可。
许多其他类型的应用程序(例如,无状态 RESTful Web 服务)不使用 HTTP 会话,并将在每个请求上重新进行身份验证。但是,在链中包含SecurityContextPersistenceFilter
以确保在每次请求后清除SecurityContextHolder
仍然很重要。
如前所述,此过滤器有两个主要任务。它负责在 HTTP 请求之间存储SecurityContext
内容,并在请求完成时清除SecurityContextHolder
。清除存储上下文的ThreadLocal
是必不可少的,因为否则可能会将一个线程替换为 servlet 容器的线程池,与特定用户的安全上下文仍然附加。然后可以在稍后阶段使用该线程,使用错误的凭证执行操作。
从 Spring Security 3.0 开始,加载和存储安全上下文的工作现在被委托给一个单独的策略接口SecurityContextRepository
。 -
UsernamePasswordAuthenticationFilter
我们现在已经看到了 Spring Security Web 配置中始终存在的三个主要过滤器。现在唯一缺少的是实际的身份验证机制,允许用户进行身份验证。此过滤器是最常用的身份验证过滤器,也是最常定制的过滤器。配置它需要三个阶段。
- 使用登录页面的URL来配
LoginUrlAuthenticationEntryPoint
,就像我们上面所做的那样,并在ExceptionTranslationFilter
上设置它。 - 实现登录页面(使用 JSP 或 MVC 控制器)。
- 在应用程序上下文中配置
UsernamePasswordAuthenticationFilter
的实例。 - 将过滤器 bean 添加到过滤器链代理(确保您注意顺序)。
- 使用登录页面的URL来配
认证成功与失败的应用流程
过滤器调用配置 AuthenticationManager
来处理每个身份验证请求。身份验证成功或身份验证失败后的目标分别由 AuthenticationSuccessHandler
和 AuthenticationFailureHandler
策略接口控制。分别的,过滤器具有这些属性以便您可以完全自定义行为。提供了一些标准实现,如 SimpleUrlAuthenticationSuccessHandler
, SavedRequestAwareAuthenticationSuccessHandler
, SimpleUrlAuthenticationFailureHandler
, ExceptionMappingAuthenticationFailureHandler
and DelegatingAuthenticationFailureHandler
。查看这些类的 Javadoc 以及 AbstractAuthenticationProcessingFilter
,以了解它们的工作原理和支持的功能。
如果认证成功后,创建的 Authentication
对象将被放入 SecurityContextHolder
中。然后将调用配置的 AuthenticationSuccessHandler
,以将用户重定向或转发到适当的目标。默认情况下,使用 SavedRequestAwareAuthenticationSuccessHandler
,这意味着在要求用户登录之前,用户将被重定向到他们请求的原始目标。
如果身份验证失败,将调用配置的 AuthenticationFailureHandler
。
请求匹配和 HttpFirewall
Servlet 规约为 HttpServletRequest 定义了一些属性,我们可能希望与之匹配来验证安全。这些是 contextPath
,servletPath
,pathinfo
和 queryString
。Spring Security 只对保护应用程序中的路径感兴趣,因此将忽略 contextPath。但是 serveltPath 和 pathInfo 没有明确规范路径如何定义,比如每段地址是否都可以包含参数。为了防止这些问题, FilterChainProxy
使用 HttpFirewall
策略去检查和包裹请求。默认情况下,未规范化的请求会自动被拒绝,删除路径参数和重复斜杠以进行匹配。因此,必须使用 FilterChainProxy 来管理安全过滤器链。
如上所述,默认策略是使用 Ant-style
路径进行匹配,这可能是大多数用户的最佳选择。该策略在 AntPathRequestMatcher
类中实现,该类使用 Spring 的 AntPathMatcher 对 servletPath 和 pathInfo 执行不区分大小写的模式匹配,忽略 queryString。
如果你需要一个更加强大的模式匹配,你可以使用正则表达式。这种策略的实现是 RegexRequestMatcher
。
HttpFirewall
还通过拒绝 HTTP 响应标头中的换行字符来阻止 HTTP 响应拆分。
默认情况下使用 StrictHttpFirewall
。此实现拒绝看似恶意的请求。你也可以自定义那些类型的请求应该被拒绝。比如,如果你希望利用 Spring MVC 的矩阵变量,你可以这样配置:
1 |
|
StrictHttpFirewall 提供有效 HTTP 方法的白名单,允许防止跨站点跟踪(XST)和 HTTP 动词篡改。默认有效的方法是"DELETE",“GET”, “HEAD”,“OPTIONS”,“PATCH”,“POST”,and “PUT”。如果你希望修改有效的有效方法,可以这样配置:
1 |
|
如果必须允许任何 HTTP 方法(不推荐),则可以使用 StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)。这将完全禁用 HTTP 方法的验证。