Spring Boot微服务JWT验证通过却返回403问题求助
我有用户服务和逻辑服务两个微服务,需求是将用户服务的登录/注册等路径设为公开,逻辑服务的API需要认证保护。目前逻辑服务遇到问题:JWT过滤器能正确验证token并打印出正确的claims,但调用受保护的API路径时仍返回403状态码。
测试方式:从用户服务获取token后,携带token请求逻辑服务接口,请求示例如下:
curl -v -X 'GET' 'http://localhost:8080/api/...' -H 'accept: */*' -H 'Authorization: Bearer token'
调试日志显示claims已正确打印,但随后多次调用BasicErrorController的error方法。控制器未添加特定安全注解,求助排查该问题的原因及解决方法。
过滤器代码
@Slf4j public class JwtFilter extends OncePerRequestFilter { private final String secretKey; public JwtFilter(String secretKey) { this.secretKey = secretKey; } @Override public void doFilterInternal(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain chain) throws ServletException, IOException { String token = httpRequest.getHeader("Authorization"); if (token == null || !token.startsWith("Bearer ")) { chain.doFilter(httpRequest, httpResponse); return; } else { token = token.substring(7); Claims claims = Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token) .getBody(); httpRequest.setAttribute("claims", claims); chain.doFilter(httpRequest, httpResponse); log.info(claims.toString()); return; } } }
安全配置代码
@Configuration @EnableWebSecurity @EnableMethodSecurity public class SecurityConfig { @Bean public Filter jwtFilter() { return new JwtFilter("..."); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth.requestMatchers("/api/public/**", "/v3/api-docs", "/v3/api-docs/**", "/swagger-ui/**") .permitAll() .anyRequest() .authenticated()) .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class) // 也尝试过添加到BasicAuthenticationFilter.class之前 .build(); } }
核心原因
你的JWT过滤器仅完成了token解析并将claims存入request属性,但没有把认证信息注入到Spring Security的安全上下文(SecurityContext)。Spring Security的authenticated()规则会检查SecurityContext中是否存在已认证的Authentication对象,即便token解析成功,上下文为空仍会判定为未认证,返回403。
解决步骤
1. 修改JWT过滤器,注入认证信息到SecurityContext
解析完claims后,需要创建Authentication对象并设置到SecurityContextHolder中,示例修改后的代码:
@Slf4j public class JwtFilter extends OncePerRequestFilter { private final String secretKey; public JwtFilter(String secretKey) { this.secretKey = secretKey; } @Override public void doFilterInternal(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain chain) throws ServletException, IOException { String token = httpRequest.getHeader("Authorization"); if (token == null || !token.startsWith("Bearer ")) { chain.doFilter(httpRequest, httpResponse); return; } token = token.substring(7); Claims claims = Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token) .getBody(); // 从claims中提取用户标识(比如用户名) String username = claims.get("username", String.class); // 从claims中提取权限(如果有),这里示例默认赋予普通用户权限 Collection<? extends GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); // 创建认证对象,密码设为null(JWT认证无需密码) Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, authorities); // 将认证对象注入安全上下文 SecurityContextHolder.getContext().setAuthentication(authentication); httpRequest.setAttribute("claims", claims); chain.doFilter(httpRequest, httpResponse); log.info(claims.toString()); } }
2. 修正安全配置中的Bean类型
将jwtFilter()的返回类型明确为JwtFilter,避免类型转换潜在问题:
@Bean public JwtFilter jwtFilter() { return new JwtFilter("..."); // 替换为实际的密钥 }
3. 可选:权限细粒度控制(如果需要)
若API需要特定角色权限,可在安全配置中细化规则,同时确保过滤器注入的authorities包含对应角色(Spring Security角色默认带ROLE_前缀):
.authorizeHttpRequests(auth -> auth.requestMatchers("/api/public/**", "/v3/api-docs/**", "/swagger-ui/**").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated())
4. 可选:线程安全优化
由于设置了SessionCreationPolicy.STATELESS,Spring Security会自动清理安全上下文,但也可手动在过滤器中添加清理逻辑,避免线程污染:
try { chain.doFilter(httpRequest, httpResponse); } finally { SecurityContextHolder.clearContext(); }
内容的提问来源于stack exchange,提问作者Arzybek

