Spring Boot3+Spring Security6 JWT权限未加载问题求助
问题场景
使用JDK 17、Spring Boot 3、Spring Security 6.0.3开发Java API服务,通过公钥验证JWT令牌,认证日志显示令牌已通过验证,但Granted Authorities始终为空,导致需权限的接口(如/api/test/**)返回403 Forbidden,即使硬编码权限也无效。
核心原因
Spring Security默认不会自动从JWT的authorities字段提取权限,需通过JwtAuthenticationConverter明确配置如何将JWT中的claims转换为GrantedAuthority对象。此前仅修改JwtDecoder的claim转换器只是修改了claims内容,但未告知Security如何将这些内容映射为权限。
解决方案
步骤1:配置自定义JwtAuthenticationConverter
创建JwtAuthenticationConverter Bean,指定从JWT的authorities字段提取权限,并处理权限格式。
步骤2:在SecurityConfig中关联转换器
在OAuth2资源服务器配置中,将自定义转换器绑定到JWT认证流程。
完整代码示例
修改后的SecurityConfig.java
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { public static final String ADMIN = "ROLE_ADMIN"; private static final String[] AUTH_WHITELIST = { "/actuator/**", }; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtDecoder jwtDecoder, JwtAuthenticationConverter jwtAuthenticationConverter) throws Exception { http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .formLogin().disable() .authorizeHttpRequests(requests -> requests .requestMatchers(AUTH_WHITELIST).permitAll() .requestMatchers("/api/test/**").hasAnyAuthority(ADMIN) .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> { jwt.decoder(jwtDecoder); jwt.jwtAuthenticationConverter(jwtAuthenticationConverter); // 绑定自定义转换器 })); return http.build(); } @Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); // 指定JWT中存储权限的字段名(默认是scope/scp,这里改为authorities) grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities"); // 取消默认的SCOPE_前缀,因为令牌中权限已为ROLE_开头 grantedAuthoritiesConverter.setAuthorityPrefix(""); JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter(); authenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); return authenticationConverter; } }
简化后的AppConfig.java
如果不需要额外处理exp/iat字段,可恢复为初始版本:
import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; @Configuration @RequiredArgsConstructor public class AppConfig { private final RsaKeyProperties rsaKeys; @Bean JwtDecoder jwtDecoder() { return NimbusJwtDecoder.withPublicKey(rsaKeys.publicKey()).build(); } }
若确实需要转换exp/iat字段,需保留自定义claim转换器,并与默认转换器组合(避免破坏标准字段处理):
@Bean JwtDecoder jwtDecoder() { NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(rsaKeys.publicKey()).build(); // 获取默认转换器,确保标准字段正常处理 Converter<Map<String, Object>, Map<String, Object>> defaultConverter = jwtDecoder.getClaimSetConverter(); jwtDecoder.setClaimSetConverter(claims -> { Map<String, Object> processedClaims = defaultConverter.convert(claims); // 自定义exp/iat转换逻辑 if (processedClaims.containsKey("exp") && processedClaims.get("exp") instanceof Date) { processedClaims.put("exp", ((Date) processedClaims.get("exp")).toInstant()); } if (processedClaims.containsKey("iat") && processedClaims.get("iat") instanceof Date) { processedClaims.put("iat", ((Date) processedClaims.get("iat")).toInstant()); } return processedClaims; }); return jwtDecoder; }
验证方式
重启服务后请求目标接口,查看日志会显示Granted Authorities=[ROLE_ADMIN, ROLE_TEST],接口将返回200状态码。
常见误区
- 仅修改
JwtDecoder的claim转换器,未配置JwtAuthenticationConverter - 未指定
authoritiesClaimName,导致Spring默认查找scope/scp字段而非authorities - 不必要保留
SCOPE_前缀,与令牌中已有的ROLE_前缀冲突
内容的提问来源于stack exchange,提问作者Hugo L.M
相关产品推荐
相关产品推荐

