Spring Security 6.x 过滤器链SecurityFilterChain是如何工作的?

一、背景:为什么需要 SecurityFilterChain

默认的过滤器链:

SecurityFilterChain是如何工作的? - 图1

1. Servlet Filter 的基础作用

在 Java Web 开发中,Servlet 提供了一个 Filter 机制,用于拦截 HTTP 请求和响应。Filter 是一个链式结构(FilterChain),可以按顺序执行多个过滤器。例如:

  • 认证过滤器:检查用户是否登录。
  • 鉴权过滤器:检查用户是否有权限访问某个资源。
  • 日志过滤器:记录请求信息。

理论上,我们可以直接编写一系列与安全相关的 Filter(如认证、鉴权等),将它们注册到 Servlet 的 FilterChain 中,就可以实现安全功能。既然如此,为什么还需要 Spring Security 的 SecurityFilterChain?这正是初学者常见的疑问。

2. 直接使用 Servlet Filter 的局限性

尽管 Servlet Filter 看起来简单直接,但在实际开发中,尤其是复杂的 Spring 应用中,直接使用 Filter 会遇到以下问题:

  • Spring 容器与 Servlet 容器的时间差:Servlet 容器(如 Tomcat)启动时,Filter 会被初始化并注册,而此时 Spring 容器(ApplicationContext)可能还未完全加载,导致 Filter 无法直接获取 Spring 管理的 Bean。
  • 代码重复与维护成本:如果每个安全相关的 Filter 都需要手动处理 Spring Bean 的获取,代码会变得冗余,违背依赖注入的原则。
  • 灵活性不足:Servlet Filter 的匹配规则仅限于 URL 模式(通过 web.xml@WebFilter 配置),无法满足复杂的匹配需求。
  • 调试困难:多个 Filter 独立运行,调试时难以统一跟踪安全相关的逻辑。

Spring Security 的 SecurityFilterChain 正是为了解决这些问题而设计的。


二、逐步分析 SecurityFilterChain 的必要性

1. 问题 1:如何在 Filter 中获取 Spring 容器中的 Bean?

问题背景
  • Servlet 容器启动时,Filter 实例会先被创建并注册到 FilterChain 中。
  • 而 Spring 容器(ApplicationContext)的 Bean 加载通常在 Servlet 容器启动之后(通过 ContextLoaderListener 或 Spring Boot 的自动配置)。
  • 这导致在 Filter 初始化时,Spring 容器中的 Bean(例如认证服务、用户详情服务等)尚未可用,无法直接通过 @Autowired 注入。
解决方案:DelegatingFilterProxy

Spring 提供了一个特殊的 Filter 实现类 DelegatingFilterProxy,用来解决这一问题:

  • 作用DelegatingFilterProxy 是一个“占位”过滤器,在 Servlet 容器启动时先注册,但不直接执行具体的过滤逻辑。
  • 工作原理
    • 在 Servlet 容器启动时,DelegatingFilterProxy 会被注册到 FilterChain 中。
    • 当 HTTP 请求到达时,DelegatingFilterProxydoFilter 方法会被调用。
    • doFilter 方法中,DelegatingFilterProxy 会从 Spring 容器(ApplicationContext)中动态查找一个真正的 Filter Bean(通过名称匹配),然后将请求委派给这个 Bean 执行。
  • 代码示例(Spring Boot 自动配置会处理以下内容,无需手动配置):
    1. // 假设 Spring 容器中有一个 Bean 名为 "springSecurityFilterChain"
    2. @Bean
    3. public Filter springSecurityFilterChain() {
    4. // 返回真正的 Filter 实例
    5. return new SomeSecurityFilter();
    6. }
    • DelegatingFilterProxy 会查找名为 springSecurityFilterChain 的 Bean,并委派给它。
好处:通过这种“委派模式”,DelegatingFilterProxy 解决了 Servlet Filter 和 Spring 容器的时间差问题,允许 Filter 访问 Spring 管理的 Bean。

2. 问题 2:是否可以为每个 Filter 都使用 DelegatingFilterProxy

问题分析

假设我们为 Spring Security 中的每个安全相关的 Filter(如认证、鉴权、CSRF 保护等)都创建一个独立的 DelegatingFilterProxy,会带来以下问题:

  • 代码重复:每个 Filter 都需要在 doFilter 方法中手动从 ApplicationContext 查找对应的 Bean,代码重复,违背了依赖注入(DI)原则。
  • 维护成本高:如果有 10 个安全相关的 Filter,就需要 10 个 DelegatingFilterProxy,增加了开发和维护成本。
  • 调试复杂:多个独立的 DelegatingFilterProxy 分布在 FilterChain 中,调试时难以统一跟踪。
解决方案:FilterChainProxy

为了解决上述问题,Spring Security 引入了 FilterChainProxy,作为所有 Spring Security 相关 Filter 的统一入口:

  • 作用FilterChainProxy 是 Spring Security 的核心过滤器,负责管理所有安全相关的 Filter,并将它们组织成一个逻辑上的过滤器链。
  • 工作原理
    • 从 Servlet FilterChain 的角度看,Spring Security 只注册了一个 Filter,即 DelegatingFilterProxy
    • DelegatingFilterProxy 将请求委派给 FilterChainProxy
    • FilterChainProxy 内部维护一个或多个 SecurityFilterChain 实例,根据请求的 URL 或其他条件选择合适的 SecurityFilterChain 来处理请求。
  • 代码示例(Spring Security 内部实现):

    1. public class FilterChainProxy implements Filter {
    2. private List<SecurityFilterChain> filterChains;
    3. @Override
    4. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    5. // 根据请求选择合适的 SecurityFilterChain
    6. SecurityFilterChain matchingChain = findMatchingChain(request);
    7. if (matchingChain != null) {
    8. matchingChain.doFilter(request, response);
    9. } else {
    10. chain.doFilter(request, response); // 继续下一个 Filter
    11. }
    12. }
    13. }

好处

  • 统一入口:所有 Spring Security 的 Filter 都通过 FilterChainProxy 管理,简化了调试。例如,可以在 FilterChainProxydoFilter 方法中设置断点,跟踪所有安全相关的逻辑。
  • 减少代码重复:只需要一个 DelegatingFilterProxy,避免为每个 Filter 重复编写查找 Bean 的逻辑。
  • 便于扩展:可以在 FilterChainProxy 中添加全局逻辑,例如日志记录、防火墙等。

3. 问题 3:如何在 FilterChainProxySecurityFilterChain 之间协作?

桥梁作用
  • FilterChainProxy 作为统一入口,负责将请求分发给合适的 SecurityFilterChain
  • SecurityFilterChain 是 Spring Security 中更细粒度的过滤器链,包含用户通过 HttpSecurity 配置的各种安全规则(如认证、授权、CSRF 保护等)。
执行流程
  1. HTTP 请求到达 Servlet 的 FilterChain
  2. DelegatingFilterProxy 触发,委派给 FilterChainProxy
  3. FilterChainProxy 根据请求(通过 RequestMatcher 匹配)选择一个 SecurityFilterChain
  4. SecurityFilterChain 执行用户配置的 Filter 列表,例如:
    • UsernamePasswordAuthenticationFilter(处理表单登录)。
    • FilterSecurityInterceptor(执行权限检查)。
  5. 如果请求通过所有 Filter,则继续执行后续的 Servlet 逻辑(例如调用 Controller)。

附加功能

  • SecurityFilterChain 的前后,Spring Security 可以添加全局处理逻辑,例如:
    • 防火墙(HttpFirewall:防范特定类型的攻击(如路径遍历攻击)。
    • 日志记录:记录每个请求的安全处理结果。

4. 问题 4:Servlet Filter 的局限性如何解决?

Servlet Filter 的局限性
  • 匹配规则单一:Servlet Filter 只能通过 URL 模式进行匹配(例如 /api/**),无法支持复杂的条件(如请求头、方法类型等)。
  • 配置不灵活:无法动态调整匹配规则或组合多个条件。
解决方案:SecurityFilterChainRequestMatcher

Spring Security 的 SecurityFilterChain 通过 RequestMatcher 接口解决了这些问题:

  • 作用RequestMatcher 是一个接口,用于定义请求匹配规则,Spring Security 提供了多种实现:
    • AntPathRequestMatcher:基于 Ant 风格的 URL 匹配(例如 /public/**)。
    • RegexRequestMatcher:基于正则表达式的 URL 匹配。
    • AndRequestMatcher/OrRequestMatcher:组合多个匹配规则。
    • 自定义 RequestMatcher:可以实现任意复杂的匹配逻辑。
  • 示例
    1. http
    2. .securityMatcher(new AntPathRequestMatcher("/api/**")) // 只匹配 /api/** 路径
    3. .authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
  • 好处
    • 灵活性:支持复杂的匹配逻辑,例如“仅对 POST 请求的 /admin/** 路径进行认证”。
    • 可扩展性:通过自定义 RequestMatcher,可以实现任意匹配规则。

三、SecurityFilterChain 的必要性总结

SecurityFilterChain 并不是简单地替代 Servlet Filter,而是为了解决直接使用 Filter 的多种问题,提供更强大、灵活和可维护的安全机制。总结其必要性如下:

  1. 解决 Spring 容器与 Servlet 容器的时间差

    • 通过 DelegatingFilterProxy,在 Filter 中动态获取 Spring Bean。
  2. 减少代码重复,提高可维护性

    • 使用 FilterChainProxy 作为统一入口,避免为每个 Filter 重复编写查找 Bean 的逻辑。
  3. 便于调试和扩展

    • FilterChainProxy 提供单一入口,便于跟踪和调试安全逻辑。
    • 可以在 SecurityFilterChain 前后添加全局处理(如防火墙)。
  4. 增强灵活性

    • 通过 RequestMatcher,支持复杂的请求匹配规则,超越了 Servlet Filter 的局限性。
  5. 模块化和可配置性

    • 用户可以通过 HttpSecurity 配置多个 SecurityFilterChain,实现模块化的安全策略(例如 API 和 Web 页面使用不同的安全规则)。

四、实际应用中的例子

假设你有一个 Web 应用,需要以下安全规则:

  • /public/**:公开访问。
  • /api/**:需要 Basic 认证。
  • /admin/**:需要表单登录和管理员权限。

使用 SecurityFilterChain,可以这样配置:

  1. @Configuration
  2. public class SecurityConfig {
  3. @Bean
  4. public SecurityFilterChain publicFilterChain(HttpSecurity http) throws Exception {
  5. http
  6. .securityMatcher("/public/**")
  7. .authorizeHttpRequests((requests) -> requests.anyRequest().permitAll());
  8. return http.build();
  9. }
  10. @Bean
  11. public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
  12. http
  13. .securityMatcher("/api/**")
  14. .authorizeHttpRequests((requests) -> requests.anyRequest().authenticated())
  15. .httpBasic(withDefaults());
  16. return http.build();
  17. }
  18. @Bean
  19. public SecurityFilterChain adminFilterChain(HttpSecurity http) throws Exception {
  20. http
  21. .securityMatcher("/admin/**")
  22. .authorizeHttpRequests((requests) -> requests.anyRequest().hasRole("ADMIN"))
  23. .formLogin(withDefaults());
  24. return http.build();
  25. }
  26. }
  • 执行流程
    1. 请求到达 FilterChainProxy
    2. 根据 securityMatcher,选择合适的 SecurityFilterChain
      • /public/home -> publicFilterChain -> 直接通过。
      • /api/data -> apiFilterChain -> 触发 Basic 认证。
      • /admin/dashboard -> adminFilterChain -> 触发表单登录并检查角色。
    3. 如果没有匹配的 SecurityFilterChain,则继续执行后续逻辑。

五、总结

Spring Security 的 SecurityFilterChain 是对 Servlet Filter 的增强和封装,解决了直接使用 Filter 的多个问题,包括容器时间差、代码重复、调试困难和匹配规则的局限性。通过 DelegatingFilterProxyFilterChainProxySecurityFilterChain 的协作,Spring Security 提供了一个强大、灵活且易于维护的安全框架。

借用网络上的一张图,原博文链接:http://www.fullstackyang.com/springan-quan-kuang-jia-xi-lie-sessionguan-li/ SecurityFilterChain是如何工作的? - 图2