Spring Security 整合 JWT 后返回 401 Unauthorized 怎么排查?

文章导读
整合 JWT 后遇到 401 Unauthorized,通常不是 Token 本身错了,而是自定义的 JWT 过滤器没有被 Spring 容器管理,或者安全配置中公开路径的规则被默认规则覆盖。
📋 目录
  1. 快速处理思路
  2. 为什么会这样
  3. 分步处理
  4. 怎么验证是否生效
  5. 常见坑
  6. 参考来源
A A

整合 JWT 后遇到 401 Unauthorized,通常不是 Token 本身错了,而是自定义的 JWT 过滤器没有被 Spring 容器管理,或者安全配置中公开路径的规则被默认规则覆盖。

先说结论:大多数 401 错误源于组件扫描遗漏导致过滤器未生效,或 SecurityFilterChain 配置未正确放行登录接口。

  • 先确认:JwtRequestFilter 是否加了 @Component 且在扫描包范围内
  • 先处理:检查 SecurityFilterChain 中 permitAll() 是否覆盖了登录和公开接口
  • 再验证:开启 Spring Security DEBUG 日志观察过滤器链执行情况

快速处理思路

建议优先通过调整日志配置定位问题,在 application.yml 中添加以下配置,以便观察安全过滤链的拦截点:

logging:
  level:
    org.springframework.security: DEBUG
    org.springframework.web.filter.CommonsRequestLoggingFilter: DEBUG

重启应用后,观察控制台日志中是否出现 "Securing GET /api/xxx" 或 "Authentication is null" 等关键信息。

为什么会这样

Spring Security 的核心机制是过滤器链(Filter Chain)。当请求进入应用时,会依次经过多个过滤器。JWT 认证通常依赖一个自定义过滤器(如 JwtRequestFilter)来解析 Token 并设置认证上下文。

如果这个自定义过滤器没有被 Spring 容器管理(例如漏了 @Component 注解,或者包扫描路径没包含它),它就不会被添加到过滤器链中。此时请求会直接落到 Spring Security 的默认拦截规则上,因为请求头中没有 Session 或有效凭证,框架就会抛出 401 错误,提示 "Full authentication is required"。

此外,Spring Security 6.x 版本迁移后,配置方式从 WebSecurityConfigurerAdapter 改为 SecurityFilterChain Bean,路径匹配方法也从 antMatchers 变更为 requestMatchers,配置疏忽容易导致公开接口被误拦截。

分步处理

1. 检查组件扫描范围

确认你的 JwtRequestFilter 类上加了 @Component 注解,并且所在包在 Spring Boot 启动类的扫描范围内。如果启动类使用了 @SpringBootApplication(scanBasePackages = {...}),确保过滤器所在的包被包含在内,否则过滤器不会被实例化。

2. 校验 SecurityFilterChain 配置(含依赖注入)

检查安全配置类中的 SecurityFilterChain Bean。确保登录接口、注册接口等公开路径使用了 permitAll()。在 Spring Security 6+ 中,需注意 JwtRequestFilter 必须通过构造函数或 @Autowired 注入,否则 addFilterBefore 会因变量未定义而编译失败。完整示例如下:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final JwtRequestFilter jwtRequestFilter;

    // 推荐构造器注入,确保过滤器已被 Spring 管理
    public SecurityConfig(JwtRequestFilter jwtRequestFilter) {
        this.jwtRequestFilter = jwtRequestFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth
            .requestMatchers("/authentication/login", "/public/**").permitAll()
            .anyRequest().authenticated()
        )
        .csrf(csrf -> csrf.disable()) // 根据实际需求配置
        .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
}

3. 补充 JwtRequestFilter 标准实现

确保过滤器继承 OncePerRequestFilter 并正确设置 SecurityContext。以下为核心实现参考:

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        // 1. 获取 Header
        final String authorizationHeader = request.getHeader("Authorization");
        String username = null;
        String jwt = null;

        // 2. 解析 Token
        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            // username = jwtTokenUtil.extractUsername(jwt); // 假设的工具类
        }

        // 3. 设置认证上下文
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            // UserDetails userDetails = ...;
            // UsernamePasswordAuthenticationToken authToken = ...;
            // SecurityContextHolder.getContext().setAuthentication(authToken);
        }
        filterChain.doFilter(request, response);
    }
}

4. 排查 CORS 预检请求

Spring Security 整合 JWT 后返回 401 Unauthorized 怎么排查?

前后端分离项目中,浏览器会先发送 OPTIONS 请求。如果安全配置未放行 OPTIONS 方法,或跨域配置被安全过滤器拦截,也会返回 401。建议在 securityFilterChain 中显式配置 cors(),或确保跨域配置在安全过滤器之前生效。

5. 检查请求头格式

确认客户端请求头中 Authorization 字段的格式正确,通常为 "Bearer <token>"。注意 Bearer 后面有一个空格,且 Token 未过期。

怎么验证是否生效

开启 DEBUG 日志后,发送一个受保护的接口请求。如果配置正确,日志中应能看到自定义过滤器执行的痕迹,且不再出现 "Authentication is null" 的提示。如果仍然 401,查看日志中是否有 "Failed to match request" 或 "Access is denied",这能帮助定位是路径匹配失败还是权限不足。

常见日志关键词对照:

日志关键词 含义 解决方向
Securing GET /api/xxx 请求进入安全过滤链 正常,继续观察后续日志
Authentication is null 上下文中无认证信息 检查 JwtRequestFilter 是否执行
Access is denied 权限不足 检查 permitAll 配置或角色权限
Failed to match request 路径匹配失败 检查 requestMatchers 路径写法

使用 Postman 等工具测试时,先测试公开接口(如登录)是否返回 200,再测试受保护接口携带 Token 是否通过。如果公开接口也返回 401,说明 SecurityFilterChain 配置未生效或路径匹配错误。

常见坑

1. 多个 SecurityFilterChain Bean 冲突

如果项目中存在多个 SecurityFilterChain 定义,且没有配置 @Order 注解,可能导致配置被覆盖或优先级混乱,部分接口意外受到默认安全策略保护。

2. Spring Security 版本迁移差异

从 Spring Security 5.x 升级到 6.x 时,antMatchers 已废弃,必须使用 requestMatchers。如果沿用旧写法,配置可能不生效导致路径放行失败。

3. 过滤器顺序错误

JWT 过滤器通常需要添加在 UsernamePasswordAuthenticationFilter 之前。如果顺序靠后,请求可能在被认证前就被其他过滤器拦截。

参考来源