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

Spring Security JWT Bearer Token权限不足403错误排查求助

JWT Bearer Token验证403权限不足问题排查

验证JWT Bearer Token时触发403错误,响应头提示Token权限范围不足,需排查Token编码权限过程或解码方式是否存在问题。

安全配置代码

@Configuration
public class SecurityConfiguration {
    private final RSAKeyConfig keys;
    public SecurityConfiguration(RSAKeyConfig keys) {
        this.keys = keys;
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Bean
    public AuthenticationManager authManager(UserDetailsService detailsService) {
        DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider();
        daoProvider.setUserDetailsService(detailsService);
        daoProvider.setPasswordEncoder(passwordEncoder());
        return new ProviderManager(daoProvider);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/auth/**").permitAll()
                        .requestMatchers("/admin/**").hasRole("ADMIN")
                        .requestMatchers("/user/**").hasAnyRole("ADMIN", "USER")
                        .anyRequest().authenticated());

        http
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtConverter())));

        http
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        return http.build();
    }

    @Bean
    public JwtDecoder jwtDecoder() {
        System.out.print("decoder");
        return NimbusJwtDecoder.withPublicKey(keys.getPublicKey()).build();
    }

    @Bean
    public JwtEncoder jwtEncoder() {
        JWK jwk = new RSAKey.Builder(keys.getPublicKey())
                .privateKey(keys.getPrivateKey()).build();
        JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));

        return new NimbusJwtEncoder(jwks);
    }

    @Bean
    public JwtAuthenticationConverter jwtConverter() {
        System.out.println("jwt converter");
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("Roles_");
        JwtAuthenticationConverter jwtconverter = new JwtAuthenticationConverter();
        jwtconverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);

        return jwtconverter;
    }
}

Token服务代码

@Service
public class TokenService {

    @Autowired
    private JwtEncoder jwtEncoder;

    @Autowired
    private JwtDecoder jwtDecoder;

    public String generateJwt(Authentication auth) {

        Instant now = Instant.now();

        String scope = auth.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(" "));

        JwtClaimsSet claims = JwtClaimsSet.builder()
                .issuer("self")
                .issuedAt(now)
                .subject(auth.getName())
                .claim("roles", scope)
                .build();

        System.out.print(claims);

        return jwtEncoder.encode(JwtEncoderParameters.from(claims))
.getTokenValue();

    }
}

密钥配置代码

@Component
@Data
public class RSAKeyConfig {

    private RSAPublicKey publicKey;

    private RSAPrivateKey privateKey;

    public RSAKeyConfig(){
        KeyPair pair = KeyGenerator.generateRsaKey();
        this.publicKey = (RSAPublicKey) pair.getPublic();
        this.privateKey = (RSAPrivateKey) pair.getPrivate();
    }
}

密钥生成器代码

public class KeyGenerator {

    public static KeyPair generateRsaKey(){

        KeyPair keyPair;

        try{
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        }catch(Exception e){
            throw new IllegalStateException();
        }

        return keyPair;

    }
}

已尝试调整安全过滤器链,但无效果。


核心问题分析

代码中存在两处关键不匹配,直接导致权限校验失败:

  1. 权限前缀不匹配:JwtAuthenticationConverter设置了前缀Roles_,但Spring Security的hasRole()方法默认匹配带ROLE_前缀的权限,两者完全不兼容。
  2. Token权限格式错误:生成Token时将多个权限拼接成单个字符串存入roles字段,而JwtGrantedAuthoritiesConverter默认期望roles是字符串数组,无法正确拆分权限。

修复方案

1. 修正Token生成逻辑,存储权限为数组

修改TokenService的generateJwt方法,将权限以数组形式存入roles claim:

public String generateJwt(Authentication auth) {
    Instant now = Instant.now();

    List<String> roles = auth.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.toList());

    JwtClaimsSet claims = JwtClaimsSet.builder()
            .issuer("self")
            .issuedAt(now)
            .subject(auth.getName())
            .claim("roles", roles)
            .build();

    System.out.print(claims);

    return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
}

2. 修正JWT转换器的权限前缀

根据你的权限格式调整前缀:

  • 如果UserDetails返回的权限是ADMIN、USER(不带前缀),则设置前缀为ROLE_,匹配hasRole()的要求;
  • 如果UserDetails返回的权限已经是ROLE_ADMIN、ROLE_USER,则设置前缀为空字符串,避免重复添加。

修改后的转换器代码:

@Bean
public JwtAuthenticationConverter jwtConverter() {
    System.out.println("jwt converter");
    JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
    jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
    // 根据实际权限格式选择前缀
    jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_"); 
    // 若权限已带ROLE_前缀,则使用:jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");
    JwtAuthenticationConverter jwtconverter = new JwtAuthenticationConverter();
    jwtconverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);

    return jwtconverter;
}

3. 验证Token内容

生成Token后,用JWT解析工具确认roles字段为数组格式,示例:

{
  "roles": ["ROLE_ADMIN", "ROLE_USER"],
  "sub": "testuser",
  "iss": "self",
  "iat": 1720000000
}

额外检查项

  • 确认UserDetailsService返回的权限与转换器前缀匹配;
  • 检查RSAKeyConfig是否每次重启生成新密钥,若如此会导致旧Token失效,建议将密钥配置到外部文件中。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 15:59:51