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

基于JWT载荷角色权限访问REST接口未授权问题排查

问题分析与解决方案

核心问题

当前配置下持续收到未授权错误,根源集中在以下三点:

1. 角色完全不匹配

安全配置要求访问/api/**的用户必须拥有User1、Admin3或TestDataUploader角色,但提供的JWT Token中实际仅包含Admin、Approve角色,两者无交集,这是直接触发未授权的核心原因。

2. Spring Security角色前缀不兼容

hasAnyRole()方法会自动为传入的角色名添加ROLE_前缀进行匹配,但你的JwtGrantedAuthoritiesConverter设置了authorityPrefix(""),导致从JWT解析出的权限是Admin、Approve(不带前缀),与hasAnyRole预期的ROLE_Admin格式不匹配。

3. 冗余权限Mapper无效且干扰

你定义的userAuthoritiesMapper是针对OIDC用户(OidcUserAuthority)的逻辑,但当前是纯JWT资源服务器场景,该Bean不会触发,反而可能导致权限处理混乱,日志也无法正确输出JWT解析出的权限。


修复步骤

步骤1:修正角色匹配逻辑

二选一调整,确保配置角色与JWT实际角色一致:

  • 方式A:修改安全配置,允许JWT中存在的角色访问:
    .requestMatchers(antMatcher("/api/**"))
    .hasAnyRole("Admin", "Approve")
    
  • 方式B:确保JWT的role声明中包含User1、Admin3或TestDataUploader中至少一个角色。

步骤2:解决前缀兼容问题

针对hasAnyRole()的前缀问题,两种处理方式:

方式A:给JWT权限添加ROLE_前缀

修改jwtAuthenticationConverter()方法,设置正确前缀:

@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
  var grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
  grantedAuthoritiesConverter.setAuthoritiesClaimName("role");
  grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_"); // 添加ROLE_前缀适配hasAnyRole
  var jwtAuthenticationConverter = new JwtAuthenticationConverter();
  jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
  return jwtAuthenticationConverter;
}

方式B:改用hasAnyAuthority()替代hasAnyRole()

直接匹配不带前缀的权限,无需修改转换器:

.requestMatchers(antMatcher("/api/**"))
.hasAnyAuthority("Admin", "Approve") // 替换hasAnyRole为hasAnyAuthority

步骤3:移除冗余权限Mapper

删除userAuthoritiesMapper() Bean,当前JWT资源服务器场景下该Bean无效,只会增加复杂度。


完整修正后的安全配置类

@Configuration
@EnableWebSecurity(debug = true)
@AllArgsConstructor
public class SecurityOAuth2Config {

  private static final Logger LOGGER = LogManager.getLogger(SecurityOAuth2Config.class);

  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

    http
      .cors(AbstractHttpConfigurer::disable)
      .headers(header -> header.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
      .csrf(AbstractHttpConfigurer::disable)
      .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
      .authorizeHttpRequests(req -> req
        .requestMatchers(antMatcher("/actuator/health")).permitAll()
        .requestMatchers(antMatcher("/actuator/info")).permitAll()
        .requestMatchers(antMatcher("/h2-console/**")).permitAll()
        .requestMatchers(antMatcher("/internal/api/**")).permitAll()
        .requestMatchers(antMatcher("/api/**"))
        .hasAnyAuthority("Admin", "Approve") // 使用hasAnyAuthority匹配实际角色
        .anyRequest().permitAll()
      )
      .oauth2ResourceServer((oauth2) -> oauth2.jwt(jwtConfigurer ->
        jwtConfigurer.jwtAuthenticationConverter(jwtAuthenticationConverter())));

    return http.getOrBuild();
  }

  @Bean
  public JwtAuthenticationConverter jwtAuthenticationConverter() {
    var grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
    grantedAuthoritiesConverter.setAuthoritiesClaimName("role");
    grantedAuthoritiesConverter.setAuthorityPrefix(""); // 保持前缀为空,配合hasAnyAuthority使用
    var jwtAuthenticationConverter = new JwtAuthenticationConverter();
    jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
    return jwtAuthenticationConverter;
  }
}

验证建议

  1. 开启@EnableWebSecurity(debug = true)后,查看控制台输出的权限匹配日志,确认JWT解析出的权限是否正确,以及/api/app接口的权限检查逻辑。
  2. 在控制器中添加权限打印代码,验证解析结果:
@GetMapping("/app")
public Collection<Response> getApplications(Authentication authentication) {
  LOGGER.info("当前用户权限:{}", authentication.getAuthorities());
  return serviceApp.loadResponse();
}

内容的提问来源于stack exchange,提问作者cUser

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 09:15:25