Skip to content
SecurityFilterChain 配置方式
概述
Spring Security 5.4 引入的组件化配置方式在 6.x 中成为标准方式。配置入口是 HttpSecurity,通过声明 SecurityFilterChain Bean 来定义安全规则,不再需要继承 WebSecurityConfigurerAdapter。所有规则通过链式调用构建过滤器链,由 Spring 容器统一管理。
基本概念
Spring Security 的核心过滤机制由一组 Filter 构成,每个 Filter 负责一项安全任务(认证、授权、CSRF 防护等)。SecurityFilterChain 封装了这种链式结构,允许以声明方式定义过滤器集合及其顺序。
与旧式适配器类相比,组件化配置将安全配置显式拆分为多个可组合的 Bean:
SecurityFilterChain:定义匹配哪些请求、经过哪些过滤器。UserDetailsService:加载用户信息。PasswordEncoder:密码编码与验证策略。AuthenticationProvider、AuthenticationEntryPoint等可按需替换。
工作原理
应用启动时,Spring Boot 自动配置会搜集所有 SecurityFilterChain Bean。每个 SecurityFilterChain 通过 securityMatcher(或默认匹配所有请求)声明自己的适用范围。请求到达后,FilterChainProxy 按照 @Order 顺序依次尝试每个 SecurityFilterChain,选择第一个 securityMatcher 匹配成功的链进行处理,后续链不再参与该请求。
过滤器链内部由 HttpSecurity 构建的过滤器组成,典型顺序为:
CsrfFilterAuthenticationFilter(如表单登录的UsernamePasswordAuthenticationFilter)ExceptionTranslationFilterAuthorizationFilter
ExceptionTranslationFilter 位于 AuthorizationFilter 之前,负责捕获认证异常和授权异常,并将其转换为合适的 HTTP 响应(如 401 或 403)。FilterChainProxy 本身已作为 Filter 注册到 Servlet 容器,并将请求委托给多个 SecurityFilterChain,因此额外创建的 Bean 无需再手动注册。
基本用法
最小配置
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**", "/auth/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}http.build() 根据当前设置生成一个 SecurityFilterChain 实例。上述配置关闭了 CSRF 防护,并指定两级授权规则:匹配到的路径允许匿名访问,其余请求需要认证。
明确的匹配器
java
@Bean
SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**")
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
);
return http.build();
}securityMatcher 将该链限定为仅对 /api/** 路径生效。未设置该方法的链默认匹配所有请求,通常作为最后一条备用链。
多条链并存
不同端点往往需要不同的安全策略。可以声明多个 SecurityFilterChain Bean,通过 @Order 控制优先级。
java
@Bean
@Order(1)
SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
return http
.securityMatcher("/api/**")
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated())
.build();
}
@Bean
@Order(2)
SecurityFilterChain webChain(HttpSecurity http) throws Exception {
return http
.securityMatcher("/**")
.formLogin(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.build();
}匹配规则:
- 按
@Order值升序排列(值越小优先级越高)。 - 对于当前请求,按顺序检查各链的
securityMatcher,命中即停止。 - 最后一条链可设置为
/**或省略securityMatcher,充当默认规则。
示例
完整配置:表单登录 + BCrypt
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**", "/login", "/error").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/home")
);
return http.build();
}
@Bean
UserDetailsService userDetailsService(PasswordEncoder encoder) {
UserDetails user = User.builder()
.username("admin")
.password(encoder.encode("123456"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}passwordEncoder.encode() 对原始密码进行 BCrypt 哈希后存入 UserDetails。认证时,Spring Security 自动使用 BCryptPasswordEncoder 比对请求密码与存储的哈希值。不要在密码前添加 {noop} 前缀与 BCrypt 混用,否则会导致编码不匹配。
如果需要同时支持多种编码格式(例如迁移期间同时存在 BCrypt 和旧式 SHA-1 哈希),可以使用 DelegatingPasswordEncoder:
java
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}存储密码时采用 {bcrypt}$2a$... 格式,该编码器会根据前缀选择对应的具体编码器。
异常处理
覆盖默认的错误响应格式,常用于 REST API:
java
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.exceptionHandling(ex -> ex
.authenticationEntryPoint((request, response, authException) -> {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"error\":\"Unauthorized\"}");
})
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"error\":\"Forbidden\"}");
})
);
return http.build();
}authenticationEntryPoint 在未认证时触发(返回 401),accessDeniedHandler 在已认证但权限不足时触发(返回 403)。
项目组织结构
Spring Boot 3 项目中常见的包布局:
text
security
├── SecurityConfig.java
├── JwtAuthenticationFilter.java
├── AuthenticationEntryPointImpl.java
├── AccessDeniedHandlerImpl.java
└── JwtService.javaSecurityConfig 作为配置入口,组合其他自定义过滤器和处理器。
注意点
- 密码编码一致性:
UserDetailsService中存储的密码格式必须与PasswordEncoder匹配。若直接使用{noop}123456,只能配合纯文本编码器,或通过DelegatingPasswordEncoder解析前缀。 WebSecurityConfigurerAdapter已移除:该类在 5.4 标记为弃用,6.x 中完全移除,继续使用会导致编译错误。@EnableWebSecurity的必要性:Spring Boot 自动配置下通常可省略,但显式声明能确保在非 Boot 环境或特定场景下配置被启用。- CSRF 防护:对外公开的 REST API 若不使用 Cookie 传递认证信息,可以关闭 CSRF。前后端分离项目中,携带
X-XSRF-TOKEN头或使用CookieCsrfTokenRepository是更严格的选择。 - 链的匹配顺序:
securityMatcher基于请求路径(支持 Ant 风格模式)和 HTTP 方法进行匹配,不直接在方法参数中处理角色、IP 地址等条件。如需更精细的控制,应在authorizeHttpRequests中进一步定义。 - 过滤器注册:自定义
Filter应通过http.addFilterBefore()或addFilterAfter()插入到过滤器链中合适位置,不应直接注册到 Servlet 容器,否则会绕过 Spring Security 上下文。
应用
- 前后端分离 API 网关:使用无状态会话、JWT 验证过滤器、静态密钥或 OAuth2 资源服务器配置。
- 传统 MVC 应用:保留表单登录、CSRF 防护、会话固定保护。
- 多端安全策略:移动端使用无状态认证,Web 管理后台使用表单登录,通过多条链分别配置。
