
本文详解 spring security 中 jwt 认证返回 401 的典型原因,聚焦于请求匹配规则(requestmatchers)配置错误、过滤器顺序不当及权限校验逻辑缺陷,并提供可落地的修复步骤与最佳实践。
本文详解 spring security 中 jwt 认证返回 401 的典型原因,聚焦于请求匹配规则(requestmatchers)配置错误、过滤器顺序不当及权限校验逻辑缺陷,并提供可落地的修复步骤与最佳实践。
在 Spring Boot + JWT 的无状态认证架构中,即使 UsernamePasswordAuthenticationToken 成功创建且 SecurityContextHolder 正确设置,仍频繁出现 401 Unauthorized 错误——这往往并非 Token 解析失败,而是授权阶段被拦截。核心问题通常隐藏在 SecurityFilterChain 的请求匹配与权限决策逻辑中。
? 关键问题定位:requestMatchers 配置陷阱
你原始配置中这一行是根本症结:
.requestMatchers(HttpMethod.PUT, "admin/**").authenticated();
它仅对 PUT 方法 + `/admin/路径** 的请求启用认证检查,而其他所有请求(如GET /api/user,POST /admin/create)**默认未被任何规则覆盖**,将落入 Spring Security 的默认拒绝策略(即denyAll()`),直接返回 401。
✅ 正确做法是采用兜底策略:先放行公开接口,再用 anyRequest().authenticated() 显式要求其余所有请求必须认证:
.authorizeHttpRequests(auth -> {
auth.requestMatchers(HttpMethod.GET, "/**").permitAll() // 所有 GET 公开
.requestMatchers(HttpMethod.POST, "/login").permitAll() // 登录接口必须是 POST(非 PUT)
.anyRequest().authenticated(); // 其余全部需认证
})
⚠️ 注意:/login 接口应为 POST(符合 REST 规范与主流 JWT 实现惯例),若前端发送的是 PUT,后端未配置对应 matcher,则该请求会被 anyRequest().authenticated() 拦截并因未认证而 401。
? 过滤器链顺序:JWT 过滤器必须在授权前生效
你的配置中:
.addFilter(authenticationFilter()) // 表单登录过滤器(处理 /login) .addFilter(new JwtAuthorizationFilter(...)) // JWT 认证过滤器
看似合理,但存在隐性风险:JwtAuthorizationFilter 继承自 OncePerRequestFilter,而 Spring Security 的 AuthorizationFilter(执行 authorizeHttpRequests 决策)必须在 JWT 过滤器之后运行,否则 SecurityContextHolder.getContext().getAuthentication() 为空,授权检查自然失败。
✅ 确保 JwtAuthorizationFilter 在 AuthorizationFilter 前注册(你当前写法正确),但更推荐显式指定位置:
.addFilterBefore(new JwtAuthorizationFilter(...), AuthorizationFilter.class)
✅ 完整修复后的安全配置(推荐)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.cors().and()
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/**").permitAll()
.requestMatchers(HttpMethod.POST, "/login").permitAll() // 修正方法 + 明确路径
.requestMatchers("/actuator/**").permitAll() // 可选:开放监控端点
.anyRequest().authenticated() // 关键:兜底认证
)
.authenticationManager(authenticationManager(authenticationConfiguration))
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JwtAuthorizationFilter(
authenticationManager(authenticationConfiguration),
userDetailsManager(),
secret), AuthorizationFilter.class)
.exceptionHandling(ex -> ex.authenticationEntryPoint(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
.headers(headers -> headers.frameOptions(FrameOptionsConfig.DISABLE))
.build();
}
? 其他常见陷阱排查清单
-
Token Header 格式:确保前端发送 Authorization: Bearer
,而非 Bearer: 或缺失空格; - Secret 一致性:JWT 签名密钥(secret)在生成 Token 和验证 Token 时必须完全一致(建议注入 @Value("${jwt.secret}"));
- UserDetails 权限非空:userDetails.getAuthorities() 返回空集合会导致 authenticated() 判定失败,务必确保角色(如 ROLE_USER)已正确赋值;
- 跨域预检(OPTIONS):若前端跨域,需确保 CorsConfiguration 允许 Authorization 头,否则预检失败导致后续请求被浏览器拦截。
✅ 总结
401 并不总意味着 Token 无效,而更可能是 “认证成功但授权失败”。优先检查三点:
- authorizeHttpRequests 是否遗漏请求路径或方法(用 anyRequest().authenticated() 避免漏配);
- JWT 过滤器是否在 AuthorizationFilter 之前执行;
- UserDetails 中的 authorities 是否非空且格式正确(如 new SimpleGrantedAuthority("ROLE_ADMIN"))。
通过精准匹配 + 显式兜底 + 过滤器有序,即可彻底解决 JWT 认证中的“假 401”问题。











