Spring Security:Servlet 过滤器(三)

3)Servlet 过滤器

  Spring Security 过滤器链是一个非常复杂且灵活的引擎。Spring Security 的 Servlet 支持基于 Servlet 过滤器,因此通常首先了解过滤器的作用会很有帮助。 下图显示了单个 HTTP 请求的处理程序的典型分层。

Spring Security:Servlet 过滤器(三)

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

  • 阻止下游过滤器或 Servlet 被调用。 在这种情况下,过滤器通常用来写 HttpServletResponse 

  • 使用下游过滤器和 Servlet 来修改 HttpServletRequest或 HttpServletResponse

   FilterChain 中 Filter s之间通过传递来发挥过滤器的作用。

   <span>Filter</span>的用法例子:

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,因此调用每个过滤器的顺序非常重要

  下面来认识 Spring Security 中的几种类型的 Filter。

  DelegatingFilterProxy

  Spring 框架提供了一个名为 DelegatingFilterProxy 的 Filter 实现,该实现允许在 Servlet 容器的生命周期和 Spring 的 ApplicationContext 之间进行桥接。 顾名思义,它是一个代理过滤器,它充当过滤器,但它并未执行实际工作,而是将过滤委托给其他人。Servlet 容器允许符合自己的标准的  Filters 注册,但自己不了解 Spring 定义的 Bean, DelegatingFilterProxy 可以通过标准的 Servlet 容器机制来被注册,但是它将所有工作委托给实现了 Filter 的 Spring Bean。

  以下是在 Spring Security 中添加 DelegatingFilterProxy 的方式,位置: AbstractSecurityWebApplicationInitializer#insertSpringSecurityFilterChain() 

/**
     * Registers the springSecurityFilterChain
     * @param servletContext the {@link ServletContext}
     */
    private void insertSpringSecurityFilterChain(ServletContext servletContext) {
        String filterName = DEFAULT_FILTER_NAME;// This is a spring name which is called "springSecurityFilterChain"
        DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(
                filterName);
        String contextAttribute = getWebApplicationContextAttribute();
        if (contextAttribute != null) {
            springSecurityFilterChain.setContextAttribute(contextAttribute);
        }
        registerFilter(servletContext, true, filterName, springSecurityFilterChain);
    }

  名为 springSecurityFilterChain 的 Bean 的创建,位置WebSecurityConfiguration#springSecurityFilterChain() 

/**
     * Creates the Spring Security Filter Chain
     * @return the {@link Filter} that represents the security filter chain
     * @throws Exception
     */
    @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    public Filter springSecurityFilterChain() throws Exception {
        boolean hasConfigurers = webSecurityConfigurers != null
                && !webSecurityConfigurers.isEmpty();
        if (!hasConfigurers) {
            WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                    .postProcess(new WebSecurityConfigurerAdapter() {
                    });
            webSecurity.apply(adapter);
        }
        return webSecurity.build();
    }

  下面是 DelegatingFilterProxy 如何传递  Filters 和  FilterChain 的图。

Spring Security:Servlet 过滤器(三)

   DelegatingFilterProxy 从 ApplicationContext 查找 Bean Filter0 ,然后调用 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);
}

  一旦 Servlet 请求到达过滤器,DelegatingFilterProxy 就会被初始化,作为其初始化的一部分,它会寻找过滤器名称,在上面的示例中,过滤器名称为 springSecurityFilterChain 

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

  ContextLoaderListener是引导侦听器。它启动和关闭 Spring 的 Root WebApplicationContext。

  ContextLoaderListener是可选的,你可以仅配置 Dispatcher Servlet 来运行 Spring 应用程序,但是对于 Spring Security,则需要一个。

  DispatcherServlet的上下文专用于与 MVC 相关的 Bean,例如控制器,视图,处理程序。它不包括与安全性相关的 bean,要使用 Spring Security,必须将与安全性相关的 bean 放在Root WebApplicationContext 中,因此需要配置  ContextLoaderListener

FilterChainProxy

  Spring Security 的 Servlet 支持包含在 FilterChainProxy 中。  FilterChainProxy是 Spring Security 提供的一个特殊过滤器,允许通过SecurityFilterChain委派许多过滤器实例。 由于FilterChainProxy是 Bean,因此通常将其包装在 DelegatingFilterProxy 中。

Spring Security:Servlet 过滤器(三)

SecurityFilterChain

   FilterChainProxy 使用 SecurityFilterChain 确定应对此请求调用哪些 Spring Security 过滤器。

Spring Security:Servlet 过滤器(三)

   <span>SecurityFilterChain</span>中的安全过滤器通常是 Bean,但它们是使用 FilterChainProxy 而不是 DelegatingFilterProxy 来注册的。  FilterChainProxy 具有直接向 Servlet 容器或 DelegatingFilterProxy 注册的许多优点。 首先,它为 Spring Security 的所有 Servlet 支持提供了一个起点。 因此,如果您想对Spring Security的 Servlet 支持进行故障排除,那么在 FilterChainProxy 中添加调试点是一个很好的起点。

  其次,由于 FilterChainProxy 对于 Spring Security 的使用至关重要,因此它可以执行不被视为可选的任务。 例如,它清除 SecurityContext 以避免内存泄漏。 它还使用 Spring Security 的 HttpFirewall 来保护应用程序免受某些类型的攻击。

  此外,它在确定何时应调用 SecurityFilterChain 时提供了更大的灵活性。 在Servlet容器中,仅根据URL调用过滤器。 但是, FilterChainProxy 可以利用 RequestMatcher 接口,根据 HttpServletRequest 中的任何内容确定调用。

  实际上, FilterChainProxy 可用于确定应使用哪个 SecurityFilterChain 。 如果你的应用程序在各种不同的方面提供了相对独立的配置。

Spring Security:Servlet 过滤器(三)

查看springSecurityFilterChain的方法

  通过注解获取已注册的 springSecurityFilterChain 的方法

1 @Autowired
2 @Qualifier("springSecurityFilterChain")
3 private Filter springSecurityFilterChain;

  在这里,使用名称为 springSecurityFilterChain 的  @Qualifier ,其类型为 Filter 而不是  FilterChainProxy 。这是因为在 WebSecurityConfiguration 中的方法 springSecurityFilterChain() ,它创建 Spring Security 的过滤器链,返回类型 Filter  ,而不是 FilterChainProxy 

   接下来需要将此对象转换为 FilterChainProxy 并调用 getFilterChains() 方法:

public void getFilters() {
    FilterChainProxy filterChainProxy = (FilterChainProxy) springSecurityFilterChain;
    List<SecurityFilterChain> list = filterChainProxy.getFilterChains();
    list.stream()
        .flatMap(chain -> chain.getFilters().stream()) 
        .forEach(filter -> System.out.println(filter.getClass()));
}

  在自己的 WebSecurityConfig (Spring Security)中启用安全性调试,该调试将记录每个请求的详细安全性信息。我们可以使用 debug 属性启用安全调试:

1 @EnableWebSecurity(debug = true)

  这样,当我们向服务器发送请求时,所有请求信息都会被记录下来。我们还将能够看到整个默认的 Spring Security 过滤器链:

Security filter chain: [
  WebAsyncManagerIntegrationFilter
  SecurityContextPersistenceFilter
  HeaderWriterFilter
  CsrfFilter
  LogoutFilter
  UsernamePasswordAuthenticationFilter
  DefaultLoginPageGeneratingFilter
  DefaultLogoutPageGeneratingFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  
  ExceptionTranslationFilter
  VBFilterSecurityInterceptor
  FilterSecurityInterceptor
]

  最后,让我们看一些重要的安全过滤器:

  •  SecurityContextPersistenceFilter :从 JSESSIONID 恢复身份验证

  •  UsernamePasswordAuthenticationFilter :执行身份验证,默认情况下响应“ /login” URL

  •  AnonymousAuthenticationFilter :当 SecurityContextHolder 中没有身份验证对象时,它将创建一个匿名身份验证对象并将其放置在此处

  •  FilterSecurityInterceptor :拒绝访问时引发异常,可能会抛出身份验证和授权异常

  •  ExceptionTranslationFilter :从 FilterSecurityInterceptor 捕获安全异常

  其中 VBFilterSecurityInterceptor 是自定义的验证过滤器,继承了 AbstractSecurityInterceptor 并实现了 javax.servlet.Filter 接口,可以在你的 Spring Security 配置 Bean中的 configure() 方法中通过 http.addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter) 来配置:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(vbFilterSecurityInterceptor, FilterSecurityInterceptor.class);
    }

参考:Spring Security 官方文档,DelegatingFilterProxy ,springSecurityFilterChain ,ContextLoaderListener and FilterChainProxyHow Spring Security Filter Chain worksOverview and Need for DelegatingFilterProxy in Spring

相关推荐