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/products | apiSecurityFilterChain | HTTP Basic | STATELESS | 禁用 |
POST /api/orders | apiSecurityFilterChain | HTTP Basic | STATELESS | 禁用 |
GET /admin/dashboard | adminSecurityFilterChain | 表单登录 | IF_REQUIRED | 启用 |
GET /product/1001 | defaultSecurityFilterChain | 表单登录(如有) | 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是如何工作的?" 回答要点:
FilterChainProxy内部维护一个List<SecurityFilterChain>,按@Order或声明顺序遍历。- 对每个请求,调用
SecurityFilterChain.matches(request),第一个返回true的链被激活。- 被激活的链执行其
getFilters()返回的全部过滤器,执行完毕后请求进入 Servlet 的剩余过滤器链或目标资源。- 因此,严格、精确的路径规则应该排在前面(更小的
@Order),宽泛的兜底规则放在后面(更大的@Order)。这与web.xml中filter-mapping的"先匹配先执行"逻辑相似,但增加了按路径动态选择过滤器组合的能力。- 在 Spring Security 5.4+ 中,推荐使用
HttpSecurity.securityMatcher(...)显式声明每条链的管辖路径,而不是依赖全局的authorizeHttpRequests中的antMatchers做路径排除。
版本说明:本章节示例基于 Spring Security 5.x 语法,使用
securityMatcher、antMatchers、@Order以及SecurityFilterChainBean 等 5.x 典型配置风格。