
在深入代码之前,我们必须先搞清楚 SecurityFilterChain 这个核心概念,以及它和我们平时用的 FilterRegistrationBean 到底有什么区别。很多配置不生效的 Bug,都是因为混淆了这两者。
如果把你的 Spring Boot 应用比作一座 “城堡”:
SecurityFilterChain 就是进入这个“金库”的唯一通道。
它本质上是一个 Bean,内部包含了一组有序的 安全过滤器链(List
当 HTTP 请求经过 Spring Security 的领地时,必须依次通过这条链上的所有关卡(如:CSRF检查 -> 身份认证 -> 权限校验 -> 异常处理),任何一关过不去,请求都会被弹回。
配置它的作用,就是为了定制这条通道的规则:
这是初学者最容易晕的地方:“我写了一个 Filter,到底该用哪种方式注册?”
| 特性 | FilterRegistrationBean | SecurityFilterChain (http.addFilter) |
|---|---|---|
| 所属层级 | Servlet 容器层 (Web Layer) 属于 Spring Boot 对原生 Servlet 的封装。 | 安全框架层 (Security Layer) 属于 Spring Security 内部的逻辑。 |
| 管辖范围 | “大门口” 拦截进入应用的所有请求。 | “安检通道内” 只负责处理安全相关的请求。 |
| 典型用途 | 通用基础设施 字符编码、CORS 跨域、Request 日志、TraceId 链路追踪。 | 安全业务逻辑 Token 校验、登录认证、动态权限控制。 |
| 能否感知安全上下文 | 不能。 它执行时,用户可能还没“登录”,它拿不到 SecurityContext。 | 能。 它是安全链的一部分,负责填充或读取用户的 Authentication 信息。 |
结论:
FilterRegistrationBean。FilterRegistrationBean 把它注册到外层,必须在 SecurityFilterChain 中通过 http.addFilterBefore() 把它安插到安全链的内部。搞懂了这一点,我们再来看具体的配置代码,一切就顺理成章了。
一个生产级的 REST API 安全链通常包含以下四个核心板块:
以下代码展示了一个通用的配置模板:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Resource
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // 自定义的 Token 过滤器
@Resource
private RestAuthenticationEntryPoint authenticationEntryPoint; // 自定义的 401 处理
@Resource
private RestAccessDeniedHandler accessDeniedHandler; // 自定义的 403 处理
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ============================================================
// 1. 基础设施配置 (Infrastructure)
// ============================================================
// 开启跨域 (允许前端调用)
.cors(Customizer.withDefaults())
// 禁用 CSRF (因为使用 Token,不需要 Cookie/Session 防护)
.csrf(AbstractHttpConfigurer::disable)
// 禁用 Session (设置为无状态,服务器不存储用户会话)
.sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 允许页面被 iframe 嵌入 (可选,通常用于报表或 H2 控制台)
.headers(c -> c.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
// ============================================================
// 2. 异常处理配置 (Exception Handling)
// ============================================================
// 将 Spring Security 的异常委托给我们自定义的 Handler,返回 JSON 而不是跳转 HTML
.exceptionHandling(c -> c
.authenticationEntryPoint(authenticationEntryPoint) // 处理 401 未登录
.accessDeniedHandler(accessDeniedHandler) // 处理 403 无权限
)
// ============================================================
// 3. 权限路由配置 (Authorization)
// ============================================================
.authorizeHttpRequests(c -> c
// A. 静态资源免鉴权
.requestMatchers(HttpMethod.GET, "/*.html", "/*.css", "/*.js", "/favicon.ico").permitAll()
// B. 公开接口免鉴权 (如登录、注册、Swagger)
.requestMatchers("/auth/login", "/auth/register", "/doc.html").permitAll()
// C. 动态白名单 (核心技巧)
// 这里可以注入一个从注解(@PermitAll)扫描出来的 URL 列表
// .requestMatchers(permitAllUrls).permitAll()
// D. 兜底规则:除了上面声明的,其他所有请求都必须认证
.anyRequest().authenticated()
);
// ============================================================
// 4. 过滤器植入 (Filter Injection)
// ============================================================
// 将自定义的 Token 过滤器加入到过滤器链中
// 建议放在 UsernamePasswordAuthenticationFilter 之前,实现“无感登录”
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
.sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
HttpSession,并给浏览器发一个 JSESSIONID Cookie。.exceptionHandling(c -> c.authenticationEntryPoint(...).accessDeniedHandler(...))
Spring Security 默认在遇到认证失败(401)或权限不足(403)时,会尝试跳转到一个默认的 HTML 报错页面。
对于前端(Vue/React)来说,这简直是灾难。前端需要的是 JSON 格式的状态码。
{ code: 401, msg: "请先登录" }。{ code: 403, msg: "权限不足" }。.authorizeHttpRequests(c -> ...)
这里定义了流量的分流策略。在工程实践中,为了避免每次写新接口都要修改 SecurityConfig 文件,我们通常采用 “动态配置” 的策略:
@PermitAll,项目启动时扫描这些方法对应的 URL,注入到配置中。application.yaml 中配置 security.ignore-urls,在此处读取并放行。注意顺序:requestMatchers 的匹配是 “先到先得” 的。必须把 permitAll 的规则写在前面,最后写 anyRequest().authenticated() 作为兜底。
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
这是鉴权逻辑生效的关键。
Session 或 Basic Auth 来识别用户。TokenAuthenticationFilter),负责从 Header 中提取 Token,解析用户 ID,验证有效性,并手动将用户信息存入 SecurityContextHolder。UsernamePasswordAuthenticationFilter 之前。这样,当请求到达后续的鉴权逻辑时,Spring Security 就会认为“该用户已登录”。