乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Spring Security 基础

    • 本章定位
    • Spring Security 是什么
    • DelegatingFilterProxy
    • 安全过滤器链
    • SecurityFilterChain
    • 过滤器执行顺序
  • 第2章 认证

    • 本章定位
    • Authentication
    • 认证流程
    • AuthenticationManager
    • ProviderManager
    • DaoAuthenticationProvider
    • UserDetails
    • UserDetailsService
    • PasswordEncoder
    • BCryptPasswordEncoder
    • DelegatingPasswordEncoder
    • 表单登录
    • SecurityContext
    • SecurityContextHolder
    • UsernamePasswordAuthenticationToken
  • 第3章 授权

    • 本章定位
    • 授权模型
    • GrantedAuthority
    • AccessDecisionManager
    • AccessDecisionVoter
    • URL 级别授权
    • 方法级别安全
    • @PreAuthorize
    • @PostAuthorize
    • @PreFilter
    • @PostFilter
    • @Secured
    • RoleHierarchy
  • 第4章 过滤器链

    • 本章定位
    • FilterChainProxy
    • SecurityContextHolderFilter
    • LogoutFilter
    • BasicAuthenticationFilter
    • CsrfFilter
    • CorsFilter
    • HeaderWriterFilter
    • AnonymousAuthenticationFilter
    • RequestCacheAwareFilter
    • ExceptionTranslationFilter
    • FilterSecurityInterceptor
  • 第5章 会话管理

    • 本章定位
    • 会话管理
    • SessionFixation
    • 会话并发控制
    • SessionCreationPolicy
    • RememberMe
  • 第6章 JWT

    • 本章定位
    • JWT
    • JwtDecoder
    • JWT 认证
    • JwtAuthenticationConverter
  • 第7章 OAuth2

    • 本章定位
    • OAuth2 基础
    • OAuth2 Client
    • OAuth2 Resource Server
    • 第三方登录配置
  • 第8章 攻击防护

    • 本章定位
    • CSRF 跨站请求伪造防护
    • CORS 跨域防护
    • Clickjacking 点击劫持防护
    • 安全响应头
    • Session Fixation 会话固定防护
  • 第9章 测试

    • 本章定位
    • 安全测试
    • @WithMockUser
    • 最佳实践

SecurityFilterChain

SecurityFilterChain 是 Spring Security 5.x 中定义安全规则边界的核心接口。它描述了一条"什么请求应该经过哪些安全过滤器"的契约:一方面通过 matches(HttpServletRequest) 判断当前请求是否属于本链的管辖范围;另一方面通过 getFilters() 提供该范围内请求需要依次执行的安全过滤器列表。


定义与作用:没有多链机制时的配置灾难

在 Spring Security 的早期单一配置模式下(如 Spring Security 3.x 的单一 <http> 元素或 4.x 的单一 WebSecurityConfigurerAdapter),所有请求共享同一套安全过滤器组合。这导致以下困境:

  • 认证方式冲突:同时启用表单登录和 HTTP Basic 时,未携带认证的 API 请求会被错误地重定向到登录页面。
  • Session 策略无法拆分:REST API 希望 STATELESS(不创建 Session),但管理后台需要 IF_REQUIRED(按需创建 Session),单链无法兼顾。
  • 过滤器顺序僵化:所有请求必须按同一套顺序经过 15+ 个过滤器,无法为某些路径跳过特定过滤器。
  • 权限规则爆炸:同一条链里需要写大量 antMatchers 排除规则,维护困难。

Spring Security 5.x 通过引入 SecurityFilterChain Bean 的多链机制,让开发者可以为不同路径声明独立的安全过滤器组合,每条链拥有独立的认证方式、授权规则、Session 策略和过滤器集合。


核心原理:接口契约与匹配机制

SecurityFilterChain 接口只定义了两个方法:

public interface SecurityFilterChain {
    // 判断当前请求是否由本链处理
    boolean matches(HttpServletRequest request);
    // 返回本链包含的安全过滤器列表
    List<Filter> getFilters();
}

在实际使用中,开发者不会直接实现这个接口,而是通过 HttpSecurity.build() 生成其默认实现 DefaultSecurityFilterChain。FilterChainProxy 在收到请求后,会按顺序遍历所有 SecurityFilterChain,第一个 matches 返回 true 的链被选中,其 getFilters() 返回的过滤器序列将被依次执行。

关键结论:

  • 多个 SecurityFilterChain 通过 @Order 控制匹配优先级,数字越小越先匹配。
  • 一旦某个链的 matches 返回 true,后续链即使也匹配也不会被考虑。
  • 如果没有任何链匹配,请求直接绕过 Spring Security 的所有过滤器,到达后续 Servlet 过滤器或 Controller。
  • 宽泛的匹配规则(如 /**)必须放在后面,否则会把后面的严格规则"吞掉"。

示例一:API 链与 Web 链的完全分离

场景说明

一个电商应用有两套界面:

  • 面向前端 SPA 的 REST API(/api/**),使用 JWT / HTTP Basic 认证,无 Session,无 CSRF。
  • 面向运营人员的管理后台(/admin/**),使用表单登录,有 Session,开启 CSRF 防护。
  • 其余路径(如首页、商品展示页)公开访问。

操作前配置:单链硬塞所有规则

@Configuration
@EnableWebSecurity
public class MonolithicSecurityConfig extends WebSecurityConfigurerAdapter { // 5.x 旧式写法

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(auth -> auth
                .antMatchers("/api/**").authenticated()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/", "/product/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults())   // 对 /api/** 也生效,不合适
            .httpBasic(Customizer.withDefaults())   // 对 /admin/** 也生效,冗余
            .csrf(csrf -> csrf
                .ignoringAntMatchers("/api/**")     // 排除 API 的 CSRF,绕弯子
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // API 也被迫创建 Session
            );
    }
}

问题分析:单链模式下所有请求都必须经过同一套过滤器。UsernamePasswordAuthenticationFilter 默认拦截 /login,如果 API 客户端未认证,它会尝试重定向,而不是返回 401。SessionCreationPolicy 是全局的,无法让 API 真正无状态。

操作后配置:三条 SecurityFilterChain 各司其职

@Configuration
@EnableWebSecurity
public class MultiChainSecurityConfig {

    @Bean
    @Order(1)
    public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/api/**") // 明确本链只管辖 /api/**
            .authorizeHttpRequests(auth -> auth
                .antMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults())
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .csrf(csrf -> csrf.disable()); // API 无状态,禁用 CSRF
        return http.build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain adminSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/admin/**")
            .authorizeHttpRequests(auth -> auth
                .anyRequest().hasRole("ADMIN")
            )
            .formLogin(form -> form
                .loginPage("/admin/login")
                .defaultSuccessUrl("/admin/dashboard")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutUrl("/admin/logout")
                .logoutSuccessUrl("/admin/login")
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .maximumSessions(1)
                .maxSessionsPreventsLogin(true)
            );
        return http.build();
    }

    @Bean
    @Order(3)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .antMatchers("/", "/product/**", "/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults());
        return http.build();
    }
}

结果分析

请求示例匹配到的链生效的认证方式Session 策略CSRF 状态
GET /api/productsapiSecurityFilterChainHTTP BasicSTATELESS禁用
POST /api/ordersapiSecurityFilterChainHTTP BasicSTATELESS禁用
GET /admin/dashboardadminSecurityFilterChain表单登录IF_REQUIRED启用
GET /product/1001defaultSecurityFilterChain表单登录(如有)IF_REQUIRED启用
  • securityMatcher 是 Spring Security 5.4+ 提供的精确匹配方式,确保每条链只关心自己的路径。
  • @Order(1) 的链优先于 @Order(2) 的链,因此 /api/admin/** 这样的重叠路径会被 API 链优先接管(实际设计中应避免这种重叠)。
  • 每条链内部可以有独立的 SessionManagementConfigurer、CsrfConfigurer 等,互不干扰。

示例二:为静态资源配置完全独立的放行链

场景说明

应用中有大量静态资源(/static/**、/js/**、/css/**、/images/**)以及 Swagger 文档(/swagger-ui/**)。这些资源不需要任何安全过滤器处理(包括 SecurityContextHolderFilter 也不应该执行,以节省性能)。

操作前配置:在默认链中逐条放行

@Bean
public SecurityFilterChain defaultChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .antMatchers("/static/**").permitAll()
            .antMatchers("/js/**").permitAll()
            .antMatchers("/css/**").permitAll()
            .antMatchers("/images/**").permitAll()
            .antMatchers("/swagger-ui/**").permitAll()
            .antMatchers("/v3/api-docs/**").permitAll()
            .anyRequest().authenticated()
        )
        .formLogin(Customizer.withDefaults());
    return http.build();
}

问题分析:虽然这些路径最终都被 permitAll() 放行,但请求仍然要依次经过整条 Security 过滤器链(SecurityContextHolderFilter、CsrfFilter、UsernamePasswordAuthenticationFilter 等共 15+ 个过滤器)。每个静态资源请求都浪费了大量 CPU 周期在无意义的过滤上。

操作后配置:用独立链让静态资源绕过所有安全过滤器

@Configuration
@EnableWebSecurity
public class ResourceOptimizedSecurityConfig {

    @Bean
    @Order(1)
    public SecurityFilterChain staticResourceChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/static/**", "/js/**", "/css/**", "/images/**")
            .authorizeHttpRequests(auth -> auth
                .anyRequest().permitAll()
            )
            // 注意:这条链仍然有默认过滤器,但数量可以通过配置进一步精简
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .csrf(csrf -> csrf.disable());
        return http.build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain swaggerChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/swagger-ui/**", "/v3/api-docs/**")
            .authorizeHttpRequests(auth -> auth
                .anyRequest().permitAll()
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .csrf(csrf -> csrf.disable());
        return http.build();
    }

    @Bean
    @Order(3)
    public SecurityFilterChain appSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .antMatchers("/", "/home").permitAll()
                .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
                .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults())
            .logout(Customizer.withDefaults());
        return http.build();
    }
}

结果分析

  • /static/** 等请求由 @Order(1) 的链匹配,只会执行该链内的少量过滤器(通过 permitAll() 快速放行)。
  • 在 Spring Security 5.7+ 中,还可以配合 WebSecurityCustomizer 或 securityMatcher 进一步优化,但 5.x 的核心思路是:通过多个 SecurityFilterChain 将不同类型的请求分流到不同安全策略中。
  • 静态资源链和 Swagger 链的 securityMatcher 采用多路径参数(逗号分隔),表示只要请求命中任一模式即由本链接管。

易错场景与面试考点

易错场景:顺序错误导致严格规则被宽松规则"吞掉"

多个 SecurityFilterChain 按 @Order 值从小到大匹配,一旦匹配成功就不再检查后续链。如果开发者把包含 permitAll() 的宽泛规则放在前面,后面更严格的规则将永远失效。

错误配置:

@Bean
@Order(1)
public SecurityFilterChain badChain(HttpSecurity http) throws Exception {
    http
        .securityMatcher("/**") // 匹配所有请求
        .authorizeHttpRequests(auth -> auth
            .anyRequest().permitAll() // 全部放行
        );
    return http.build();
}

@Bean
@Order(2)
public SecurityFilterChain adminChain(HttpSecurity http) throws Exception {
    http
        .securityMatcher("/admin/**")
        .authorizeHttpRequests(auth -> auth
            .anyRequest().hasRole("ADMIN")
        );
    return http.build();
}

后果:adminChain 永远不会被匹配到,因为 @Order(1) 的 badChain 已经用 /** 拦截了所有请求并将其放行。/admin/** 路径失去了应有的保护。

正确配置:

@Bean
@Order(1)
public SecurityFilterChain adminChain(HttpSecurity http) throws Exception {
    http
        .securityMatcher("/admin/**")
        .authorizeHttpRequests(auth -> auth
            .anyRequest().hasRole("ADMIN")
        );
    return http.build();
}

@Bean
@Order(2)
public SecurityFilterChain defaultChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .anyRequest().permitAll()
        );
    return http.build();
}

面试考点:面试官常问 "Spring Security 中多个 SecurityFilterChain 是如何工作的?" 回答要点:

  1. FilterChainProxy 内部维护一个 List<SecurityFilterChain>,按 @Order 或声明顺序遍历。
  2. 对每个请求,调用 SecurityFilterChain.matches(request),第一个返回 true 的链被激活。
  3. 被激活的链执行其 getFilters() 返回的全部过滤器,执行完毕后请求进入 Servlet 的剩余过滤器链或目标资源。
  4. 因此,严格、精确的路径规则应该排在前面(更小的 @Order),宽泛的兜底规则放在后面(更大的 @Order)。这与 web.xml 中 filter-mapping 的"先匹配先执行"逻辑相似,但增加了按路径动态选择过滤器组合的能力。
  5. 在 Spring Security 5.4+ 中,推荐使用 HttpSecurity.securityMatcher(...) 显式声明每条链的管辖路径,而不是依赖全局的 authorizeHttpRequests 中的 antMatchers 做路径排除。

版本说明:本章节示例基于 Spring Security 5.x 语法,使用 securityMatcher、antMatchers、@Order 以及 SecurityFilterChain Bean 等 5.x 典型配置风格。

上一页
安全过滤器链
下一页
过滤器执行顺序