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; } }
已尝试调整安全过滤器链,但无效果。
核心问题分析
代码中存在两处关键不匹配,直接导致权限校验失败:
- 权限前缀不匹配:
JwtAuthenticationConverter设置了前缀Roles_,但Spring Security的hasRole()方法默认匹配带ROLE_前缀的权限,两者完全不兼容。 - 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
相关产品推荐
相关产品推荐

