参考:
Spring Security Reference
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 }
因为 Servlet Filter 只能影响下游的 Filter 或 Servlet
所以 Servlet Filter 的顺序很重要
Spring 提供了一个 Servlet Filter 实现:DelegatingFilterProxy
它是 Servlet 容器生命周期与 Spring 的 ApplicationContext 之间的桥梁
Servlet 容器允许按照 Servlet 标准注册 Filter
但无法发现 Spring 定义的 Bean
可以使用标准的 Servlet 机制将 DelegatingFilterProxy 注册到 Servlet 容器
然后将所有工作委托给实现了 Filter 接口的 Spring Bean
伪代码
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { // 懒加载被注册为 Spring Bean 的【Bean Filter0】 // 下边的 delegate 是指向【Bean Filter0】的引用 Filter delegate = getFilterBean(someBeanName); // 将工作委托给【Bean Filter0】 delegate.doFilter(request, response); }
使用 DelegatingFilterProxy 的另一个好处是,可以延迟加载 Filter Bean 实例
这很重要,因为 Servlet 容器需要在启动前注册好 Filter 实例
但 Spring 一般会使用 ContextLoaderListener 来加载 Spring Bean
而 ContextLoaderListener 会在 Filter 实例需要被注册到 Spring 容器时才会开始加载
Spring Security 的 Servlet 支持,都是由 FilterChainProxy 来完成的
FilterChainProxy 是 Spring Security 提供的一个特殊的 Filter
它会通过 SecurityFilterChain 把工作委托给多个 Filter 实例
因为 FilterChainProxy 是一个 Spring Bean
因此一般会将其包装在的 DelegatingFilterProxy 中
此时 FilterChainProxy 就是之前提到的【Bean Filter0】
package org.springframework.web.filter; public class DelegatingFilterProxy extends GenericFilterBean { ... @Nullable private WebApplicationContext webApplicationContext; @Nullable private String targetBeanName; private boolean targetFilterLifecycle = false; // 通常为 FilterChainProxy @Nullable private volatile Filter delegate; ... @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 251 // Lazily initialize the delegate if necessary. Filter delegateToUse = this.delegate; if (delegateToUse == null) { synchronized (this.delegateMonitor) { delegateToUse = this.delegate; if (delegateToUse == null) { WebApplicationContext wac = findWebApplicationContext(); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: " + "no ContextLoaderListener or DispatcherServlet registered?"); } delegateToUse = initDelegate(wac); } this.delegate = delegateToUse; } } // Let the delegate perform the actual doFilter operation. invokeDelegate(delegateToUse, request, response, filterChain); } ... protected Filter initDelegate(WebApplicationContext wac) throws ServletException { // 335 String targetBeanName = getTargetBeanName(); Assert.state(targetBeanName != null, "No target bean name set"); /* 通过名称 targetBeanName 从 Spring 容器中获取 Bean targetBeanName 通常为 "springSecurityFilterChain" delegate 通常为 FilterChainProxy */ Filter delegate = wac.getBean(targetBeanName, Filter.class); if (isTargetFilterLifecycle()) { delegate.init(getFilterConfig()); } return delegate; } protected void invokeDelegate( Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 356 // 将工作委托给 delegate,delegate 通常为 FilterChainProxy delegate.doFilter(request, response, filterChain); } ... }
一个 FilterChainProxy 对应多个 SecurityFilterChain
FilterChainProxy 根据请求选择一个 SecurityFilterChain
一个 SecurityFilterChain 对应着一个 Filter 序列
SecurityFilterChain 中的 Filter 通常也是 Spring Bean
但没有直接将它们注册到 DelegatingFilterProxy,而是注册到了 FilterChainProxy
是 FilterChainProxy 对象遍历调用的 Filter.doFilter(),不是 SecurityFilterChain 对象
SecurityFilterChain 只是用于分组存放多个 Filter 的容器
相对于将 Filter 直接注册到 Servlet 容器或 DelegatingFilterProxy
将其通过 SecurityFilterChain 注册到 FilterChainProxy 有下面这些好处
每个继承了 WebSecurityConfigurerAdapter 的配置类对应一套安全策略
即对应一个 SecurityFilterChain
一个经过 FilterChainProxy 的请求最多只会匹配到其中一个 SecurityFilterChain
如果一个请求可以被多个 SecurityFilterChain 匹配到,那么会调用第一个匹配到的 SecurityFilterChain
各个 SecurityFilterChain 相互独立
甚至可以配置一个没有 Filter 的 SecurityFilterChain
表示某些请求只做 FilterChainProxy 的公共处理,不做额外的安全工作
FilterChainProxy 通过 SecurityFilterChain 获取对应的 Filter 序列
这些 Filter 的顺序很重要,通常这些顺序由 Spring 根据其类型固定,客户端程序员不需要知道
下边按顺序列出 Spring Security Filter
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 响应
如果捕获到的既不是 AccessDeniedException 也不是 AuthenticationException
ExceptionTranslationFilter 不会做任何处理,直接再次抛出原来的异常
只有 FilterSecurityInterceptor、SwitchUserFilter 在 ExceptionTranslationFilter 之后
其它 Spring Security 的 Filter 都在前面,而只有后边的 Filter 抛异常才能被捕获
一般的,由 FilterSecurityInterceptor 来统一抛出安全相关的异常
package org.springframework.security.web.access; public class ExceptionTranslationFilter extends GenericFilterBean implements MessageSourceAware { ... private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { // 119 try { chain.doFilter(request, response); } catch (IOException ex) { throw ex; } catch (Exception ex) { // Try to extract a SpringSecurityException from the stacktrace Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex); RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer .getFirstThrowableOfType(AuthenticationException.class, causeChain); if (securityException == null) { securityException = (AccessDeniedException) this.throwableAnalyzer .getFirstThrowableOfType(AccessDeniedException.class, causeChain); } if (securityException == null) { rethrow(ex); } if (response.isCommitted()) { throw new ServletException("Unable to handle the Spring Security Exception " + "because the response is already committed.", ex); } handleSpringSecurityException(request, response, chain, securityException); } } ... }
一个 SecurityFilterChain 对应一个 AuthenticationManager
一个 AuthenticationManager 对应多个 AuthenticationProvider
Spring Boot 自动配置