
本文详解 Spring Boot 中 JWT 认证返回 401 的典型原因,聚焦于 requestMatchers 配置错误、过滤器顺序不当及权限规则覆盖不足三大核心问题,并提供可立即生效的修复代码与最佳实践。
本文详解 spring boot 中 jwt 认证返回 401 的典型原因,聚焦于 `requestmatchers` 配置错误、过滤器顺序不当及权限规则覆盖不足三大核心问题,并提供可立即生效的修复代码与最佳实践。
在基于 Spring Security + JWT 的无状态认证架构中,即使 JwtAuthorizationFilter 成功解析 Token 并正确设置 SecurityContextHolder(如日志中 auth.getName() 输出用户名),仍返回 401 Unauthorized,往往并非 Token 验证失败,而是 请求未通过 Spring Security 的授权决策链(Authorization Decision)。根本原因通常隐藏在 SecurityFilterChain 的配置细节中。
? 关键问题定位:授权规则未覆盖实际请求路径
原配置中:
.authorizeHttpRequests(auth -> {
auth.requestMatchers(HttpMethod.GET, "/**").permitAll()
.requestMatchers(HttpMethod.PUT, "/login").permitAll()
.requestMatchers(HttpMethod.PUT, "admin/**").authenticated(); // ❌ 问题所在
})该配置存在两个致命缺陷:
- HTTP 方法不匹配:登录接口实际使用 POST(标准 REST 实践),但配置为 HttpMethod.PUT,导致 /login 请求无法被 permitAll() 放行,进而被后续 .authenticated() 规则拦截;
- 路径匹配范围过窄:"admin/**" 仅匹配以 admin/ 开头的 PUT 请求,而真实管理接口可能使用 GET、POST 或其他方法(如 GET /admin/users),这些请求完全未被任何 requestMatchers 覆盖,默认触发 denyAll() 行为,直接返回 401。
✅ 正确做法是采用分层授权策略:
.authorizeHttpRequests(auth -> {
auth.requestMatchers(HttpMethod.GET, "/**").permitAll() // 公开 GET 资源
.requestMatchers(HttpMethod.POST, "/login").permitAll() // ✅ 修正为 POST
.requestMatchers("/actuator/**").permitAll() // 可选:开放健康检查端点
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() // 可选:开放 Swagger
.anyRequest().authenticated(); // ✅ 所有其他请求需认证
})anyRequest().authenticated() 是关键——它作为兜底规则,确保所有未显式放行的请求都进入认证校验流程,而非被静默拒绝。
⚠️ 过滤器顺序陷阱:JWT 过滤器必须在授权过滤器之前
Spring Security 过滤器链严格依赖执行顺序。JwtAuthorizationFilter 的职责是解析 Token 并设置 Authentication 到 SecurityContext;而授权决策(AuthorizationFilter)发生在其后。若顺序颠倒,授权逻辑将看到空的 Authentication,直接判定为未认证。
检查你的配置:
.addFilter(authenticationFilter()) // 处理登录表单或 JSON 登录 .addFilter(new JwtAuthorizationFilter(...)) // ✅ 必须在 authorizeHttpRequests 之前
确保 JwtAuthorizationFilter 在 AuthorizationFilter(由 authorizeHttpRequests 自动注册)之前执行。Spring Security 默认顺序中,自定义 JWT 过滤器应通过 .addFilterBefore() 或明确插入位置来保证——当前写法虽常见,但仍建议显式指定:
.addFilterBefore(new JwtAuthorizationFilter(...), UsernamePasswordAuthenticationFilter.class)
?️ 其他必备检查项
- Token Header 格式:确认前端发送的 Header 为 Authorization: Bearer <token>,且 TOKEN_PREFIX = "Bearer "(注意空格);
- Secret 密钥一致性:生成 Token 与验证 Token 使用的 secret 字符串必须完全相同(包括大小写、空格);
- UserDetails 加载完整性:userDetailsService.loadUserByUsername(userName) 返回的 UserDetails 对象,其 getAuthorities() 必须包含非空权限集合(如 AuthorityUtils.createAuthorityList("ROLE_USER")),否则 authenticated() 规则可能因无权限而失败;
- Session 策略:sessionCreationPolicy(SessionCreationPolicy.STATELESS) 已正确配置,避免与 Cookie Session 冲突。
✅ 最终推荐配置片段
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors(Customizer.withDefaults())
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/", "/api/public/**").permitAll()
.requestMatchers(HttpMethod.POST, "/login", "/register").permitAll()
.requestMatchers("/actuator/**", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
.anyRequest().authenticated() // ? 核心兜底规则
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authenticationManager(authenticationManager(authenticationConfiguration))
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JwtAuthorizationFilter(
authenticationManager(authenticationConfiguration),
userDetailsManager(),
secret), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(ex -> ex
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
);
return http.build();
}通过修正授权规则粒度、确保过滤器顺序、验证 Token 传输细节,即可彻底解决“Token 解析成功却返回 401”的经典问题。记住:Spring Security 的授权不是“匹配即放行”,而是“匹配即应用规则;未匹配则拒绝”。anyRequest().authenticated() 是无状态 JWT 应用的黄金准则。

















