Spring Security 的 Servlet 支持是基于 Servlet 过滤器的。本文图片来自 Spring 官网
下图显示了单个HTTP请求的处理程序典型分层:
- 客户端向应用程序发送请求,容器创建一个包含过滤器和 Servlet 的 FilterChain,这些过滤器和 Servlet 应该基于请求 URL 的路径来处理 HttpServletRequest。
- 在 Spring MVC 应用程序中,Servlet 是 DispatcherServlet 的一个实例。
- 一个 Servlet 最多可以处理一个 HttpServletRequest 与 HttpServletResponse。
- 然而可以使用多个 Filter。
- 每层过滤器可以修改下游过滤器和 Servlet 使用的 HttpServletRequest 或 HttpServletResponse
Filter 的强大功能来自传递给它的 FilterChain:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// do something before the rest of the application
chain.doFilter(request, response); // invoke the rest of the application
// do something after the rest of the application
}
复制代码
注意: 因为 Filter 只影响下游的 Filters 和 Servlet,所以调用每个 Filter 的顺序非常重要。
DelegatingFilterProxy
Spring 提供了一个名为 DelegatingFilterProxy 的过滤器实现,它允许在 Servlet 容器的生命周期和 Spring 的 ApplicationContext 之间进行桥接。Servlet 容器允许使用它自己的标准注册过滤器,但是它不知道 Spring 定义的 bean。DelegatingFilterProxy 可以通过标准的 Servlet 容器机制注册 Filter,同时可以将所有的工作委托给实现 Filter 的 Spring Bean。
DelegatingFilterProxy 的加入使得 Bean Filter0 的实例可以正常进行工作。
DelegatingFilterProxy 伪代码:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// Lazily get Filter that was registered as a Spring Bean
// For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
Filter delegate = getFilterBean(someBeanName);
// delegate work to the Spring Bean
delegate.doFilter(request, response);
}
复制代码
FilterChainProxy
Spring Security 的 Servlet 支持包含在 FilterChainProxy 中。FilterChainProxy 是 Spring Security 提供的一个特殊的过滤器,它允许通过 SecurityFilterChain 委托给许多 Filter 实例。由于FilterChainProxy 是一个 Bean,它通常被封装在 DelegatingFilterProxy 中。
SecurityFilterChain
SecurityFilterChain 被 FilterChainProxy 用来确定应该为这个请求调用哪个 Spring 安全过滤器。
注意:
SecurityFilterChain 中的安全过滤器通常是 bean,但是它们注册在 FilterChainProxy 而不是 DelegatingFilterProxy 上。FilterChainProxy 为直接注册 Servlet 容器或 DelegatingFilterProxy 提供了许多优势。首先,它为所有 Spring Security 的 Servlet 支持提供了一个起点。因此,如果在 Spring Security 的 Servlet 支持方面排除故障,那么在 FilterChainProxy 中添加一个调试点是一个很好的开始。
此外,它在确定何时应该调用 SecurityFilterChain 方面提供了更多的灵活性。在 Servlet 容器中,只根据 URL 调用过滤器。然而,FilterChainProxy 可以通过利用 RequestMatcher 接口来根据 HttpServletRequest 中的任何内容来确定调用。
上图所示,FilterChainProxy 决定应该使用哪个 SecurityFilterChain。只有第一个匹配的 SecurityFilterChain 将被调用。如果请求的 URL 是 /api/messages/,它将首先匹配 SecurityFilterChaing 的 /api/** 模式,因此只有 SecurityFilterChaing0 将被调用,即使它也匹配 SecurityFilterChainN。如果请求一个 /messages,它将不匹配 SecurityFilterChaing0 的 /api/** 模式,因此 FilterChainProxy 将继续尝试每个 SecurityFilterChain。假设没有其他 SecurityFilterChain 实例匹配,SecurityFilterChainN 将被调用。
Security Filters
Security Filter 通过 SecurityFilterChain API 插入到 FilterChainProxy 中。过滤器的顺序很重要。通常不需要知道 Spring Security 的 Filters 的顺序。但是,有时知道顺序是有益的:
顺序如下:
- ChannelProcessingFilter
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- HeaderWriterFilter
- CorsFilter
- CsrfFilter
- LogoutFilter
- OAuth2AuthorizationRequestRedirectFilter
- Saml2WebSsoAuthenticationRequestFilter
- X509AuthenticationFilter
- AbstractPreAuthenticatedProcessingFilter
- CasAuthenticationFilter
- OAuth2LoginAuthenticationFilter
- Saml2WebSsoAuthenticationFilter
- UsernamePasswordAuthenticationFilter
- OpenIDAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- DefaultLogoutPageGeneratingFilter
- ConcurrentSessionFilter
- DigestAuthenticationFilter
- BearerTokenAuthenticationFilter
- BasicAuthenticationFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- JaasApiIntegrationFilter
- RememberMeAuthenticationFilter
- AnonymousAuthenticationFilter
- OAuth2AuthorizationCodeGrantFilter
- SessionManagementFilter
- ExceptionTranslationFilter
- FilterSecurityInterceptor
- SwitchUserFilter
处理安全异常
- ExceptionTranslationFilter 允许将 AccessDeniedException 和 AuthenticationException 转换为 HTTP 响应。
- ExceptionTranslationFilter 作为一个安全过滤器被插入到 FilterChainProxy 中。
-
首先,ExceptionTranslationFilter 通过调用
FilterChain.doFilter(request, response)
来唤醒应用程序的其余部分。 -
如果用户未经过身份验证或是 AuthenticationException,则启动认证:
- 清除 SecurityContextHolder
- HttpServletRequest 保存在 RequestCache 中。当用户成功通过身份验证时,将使用 RequestCache重放原始请求
- AuthenticationEntryPoint 用于从客户端请求凭据。例如,它可能重定向到一个登录页面或发送一个 WWW-Authenticate 头。
-
否则,如果是 AccessDeniedException,则拒绝访问。调用 AccessDeniedHandler 来处理拒绝访问。
ExceptionTranslationFilter 伪代码如下:
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
if (!authenticated || ex instanceof AuthenticationException) {
startAuthentication();
} else {
accessDenied();
}
}
复制代码