A Review of Filters

Spring Security对Servlet的支持是基于Servlet过滤器的.

Architecture - 图1
客户端向应用程序发送一个请求,容器创建一个FilterChain,其中包含过滤器和Servlet,应该根据请求URI的路径来处理HttpServletRequest。在Spring MVC应用程序中,Servlet是DispatcherServlet的一个实例。一个Servlet最多可以处理一个HttpServletRequest和HttpServletResponse。然而,可以使用一个以上的过滤器来:

  • 防止下游的过滤器或Servlet被调用。在这种情况下,Filter通常会写入HttpServletResponse。
  • 修改下游过滤器和Servlet使用的HttpServletRequest或HttpServletResponse。
    1. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    2. // do something before the rest of the application
    3. chain.doFilter(request, response); // invoke the rest of the application
    4. // do something after the rest of the application
    5. }
    由于一个Filter只影响到下游的Filter和Servlet,所以每个Filter的调用顺序是非常重要的。

    DelegatingFilterProxy

    Spring提供了一个名为DelegatingFilterProxy的Filter实现,允许在Servlet容器的生命周期和Spring的ApplicationContext之间建立桥梁。Servlet容器允许使用自己的标准来注册Filter,但它不知道Spring定义的Bean。DelegatingFilterProxy可以通过标准的Servlet容器机制来注册,但将所有工作委托给实现Filter的Spring Bean。

下面是DelegatingFilterProxy如何融入过滤器和FilterChain的图片:
Architecture - 图2
DelegatingFilterProxy从ApplicationContext查找Bean Filter0,然后调用Bean Filter0。DelegatingFilterProxy的伪代码见下文:

  1. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
  2. // Lazily get Filter that was registered as a Spring Bean
  3. // For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
  4. Filter delegate = getFilterBean(someBeanName);
  5. // delegate work to the Spring Bean
  6. delegate.doFilter(request, response, chain);
  7. }

DelegatingFilterProxy的另一个好处是,它允许延迟寻找Filter Bean实例。这一点很重要,因为在容器启动之前,容器需要注册Filter实例。然而,Spring通常使用ContextLoaderListener来加载Spring Bean,直到需要注册Filter实例之后才会完成。

FilterChainProxy

Spring Security的Servlet支持包含在FilterChainProxy中。FilterChainProxy是Spring Security提供的一个特殊的Filter,允许通过SecurityFilterChain委托给许多Filter实例。由于FilterChainProxy是一个Bean,它通常被包裹在DelegatingFilterProxy中。

Architecture - 图3

SecurityFilterChain

SecurityFilterChain由FilterChainProxy使用,以确定该请求应调用哪些Spring安全过滤器。

Architecture - 图4

SecurityFilterChain中的安全过滤器通常是Bean,但它们是通过FilterChainProxy而不是DelegatingFilterProxy注册的。与直接向Servlet容器或DelegatingFilterProxy注册相比,FilterChainProxy有很多优势。首先,FilterChainProxy为Spring Security的所有Servlet支持提供了一个起点。出于这个原因,如果你试图对Spring Security的Servlet支持进行故障诊断,在FilterChainProxy中添加一个调试点是一个很好的开始。

其次,由于FilterChainProxy是Spring Security使用的核心,它可以执行一些不被视为可选的任务。例如,它清除了SecurityContext以避免内存泄漏。它还应用Spring Security的HttpFirewall来保护应用程序免受某些类型的攻击。 此外,它在确定何时应该调用SecurityFilterChain方面提供了更大的灵活性。在Servlet容器中,过滤器仅根据URL被调用。然而,FilterChainProxy可以通过利用RequestMatcher接口,根据HttpServletRequest中的任何内容确定调用。 事实上,FilterChainProxy可以用来决定应该使用哪个SecurityFilterChain。这允许为你的应用程序的不同片断提供一个完全独立的配置。

Architecture - 图5
在多个SecurityFilterChain图中,FilterChainProxy决定应该使用哪个SecurityFilterChain。只有第一个匹配的SecurityFilterChain才会被调用。如果请求的URL是/api/messages/,它将首先与SecurityFilterChain0的模式/api/相匹配,所以只有SecurityFilterChain0会被调用,尽管它也与SecurityFilterChainn相匹配。如果请求的URL是/messages/,它将不会与SecurityFilterChain0的/api/模式匹配,所以FilterChainProxy将继续尝试每个SecurityFilterChain。假设没有其他的SecurityFilterChain实例与之匹配,就会调用SecurityFilterChainn。
请注意,SecurityFilterChain0只配置了三个安全过滤器实例。然而,SecurityFilterChainn却配置了四个安全过滤器。值得注意的是,每个SecurityFilterChain都可以是唯一的,并且是单独配置的。事实上,如果应用程序希望Spring Security忽略某些请求,那么一个SecurityFilterChain可能有零个安全过滤器。

Security Filters

安全过滤器是通过SecurityFilterChain API插入FilterChainProxy中的。过滤器的顺序很重要。通常没有必要知道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

    Handling Security Exceptions

    ExceptionTranslationFilter允许将AccessDeniedException和AuthenticationException翻译成HTTP响应。 ExceptionTranslationFilter作为安全过滤器之一被插入到FilterChainProxy中。

Architecture - 图6

  1. 首先,ExceptionTranslationFilter调用FilterChain.doFilter(request, response)来调用应用程序的其他部分。
  2. 判断是认证异常还是访问被拒绝异常?

image.png

  1. 如果是认证异常,则开始认证过程:清除当前认证信息 securityContextHolder -> 缓存当前的请求,当用户成功认证后,RequestCache被用来重放原始请求 -> 开始认证,返回认证成功或者重定向到登录页面。

image.png

  1. 否则,如果它是一个AccessDeniedException,那么就是Access Denied。AccessDeniedHandler被调用来处理拒绝访问。

image.png :::info 如果应用程序没有抛出AccessDeniedException或AuthenticationException,那么ExceptionTranslationFilter就不会做任何事情。 ::: ExceptionTranslationFilter的伪代码看起来是这样的:

  1. try {
  2. filterChain.doFilter(request, response);
  3. } catch (AccessDeniedException | AuthenticationException ex) {
  4. if (!authenticated || ex instanceof AuthenticationException) {
  5. startAuthentication();
  6. } else {
  7. accessDenied();
  8. }
  9. }

你会记得在《A Review of Filters》中,调用FilterChain.doFilter(request, response)相当于调用了应用程序的其他部分。这意味着,如果应用程序的其他部分(即FilterSecurityInterceptor或方法安全)抛出一个AuthenticationException或AccessDeniedException,它将在这里被捕获和处理。如果用户没有被认证,或者是一个AuthenticationException,那么就开始认证。否则,拒绝访问。