简述
在使用 Spring Security 配置授权规则时,配置 permitAll().anyRequest().authenticated() 再结合以白名单基本是通用的做法,但是白名单中一定要包含 Spring Boot 框架使用的 /error 路径,否则 Spring Boot 框架内部对它的调用会被 Spring Security 拦截,并在浏览器端响应令人困惑的 403
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.cors(AbstractHttpConfigurer::disable)
// 禁用CSRF保护
.csrf(AbstractHttpConfigurer::disable)
// 禁用Session
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// 配置授权规则
.authorizeHttpRequests(auth -> auth
.requestMatchers(WHITELIST).permitAll() // 白名单里一定要包括 /error
.anyRequest().authenticated()
)
// 添加JWT过滤器
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
详细分析
通过查看 Spring Security 日志分析,可以还原这一故障
Securing GET /users/bae9a650-e249-4526-83e4-d5830c330f29?_t=1756892873084
Secured GET /users/bae9a650-e249-4526-83e4-d5830c330f29?_t=1756892873084
# 可以看到,第一个请求进入了Spring Security的过滤器链并被处理完毕
Securing GET /error?_t=1756892873084
# 第一个请求/users在处理过程中抛出了一个异常。Spring Boot的默认错误处理机制会捕获这个异常,并发起一个内部转发到/error路径来处理并生成错误响应
Set SecurityContextHolder to anonymous SecurityContext
Pre-authenticated entry point called. Rejecting access
# /error请求进入安全过滤器链,此时SecurityContext是空的
# Spring Security尝试将其设置为一个匿名用户
# 由于.anyRequest().authenticated()配置,匿名用户不属于已认证用户,访问被拒绝,返回403
所以,前端调用方看到的 403 实际上是这个 /error 请求返回的,而不是原始请求直接返回的
- 原始请求 /users 处理失败,可能是 404 或因为某种原因在逻辑中抛出了异常
- Spring Boot 错误处理介入:异常被抛出后,Spring Boot 的 BasicErrorController 会捕获它,并内部转发到 /error 来生成一个 JSON 错误响应
- /error 路径被安全链保护,导致 /error 路径也纳入了需要认证的范围内
- 错误处理本身触发 403:由于错误请求是内部发起的,没有携带认证信息(如 JWT Token),无法通过安全校验,导致对 /error 的访问被拒,最终生成了一个 403 Forbidden 响应,掩盖了原始的 404 错误
图例
Spring Boot 框架内部转发触发第二轮 Spring Security 安全过滤,访问被拒绝
浏览器 GET /users
│
▼
服务器 Security Filter Chain (JWT验证通过) ✅
│
▼
Controller (抛出异常) 💥
│
│ (内部转发:request.getRequestDispatcher("/error").forward(...))
└─────────────────────────────────────┐
▼
Security Filter Chain **再次被调用**
│
▼
当前用户是匿名用户 ❌
│
▼
授权失败,返回403 🚫