启用 Spring Security 后,原本爆 404 不存在的接口变为了爆 403 原因解析

0 阅读2分钟

简述

在使用 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 请求返回的,而不是原始请求直接返回的

  1. 原始请求 /users 处理失败,可能是 404 或因为某种原因在逻辑中抛出了异常
  2. Spring Boot 错误处理介入:异常被抛出后,Spring Boot 的 BasicErrorController 会捕获它,并内部转发到 /error 来生成一个 JSON 错误响应
  3. /error 路径被安全链保护,导致 /error 路径也纳入了需要认证的范围内
  4. 错误处理本身触发 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 🚫