整合 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 预检请求
前后端分离项目中,浏览器会先发送 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 之前。如果顺序靠后,请求可能在被认证前就被其他过滤器拦截。