You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot微服务JWT验证通过却返回403问题求助

问题:JWT验证通过但受保护API返回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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 15:17:11