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

Spring Boot3+Spring Security6 JWT权限未加载问题求助

Spring Security 6 无法从JWT加载权限(Granted Authorities为空)解决方案

问题场景

使用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状态码。

常见误区

  1. 仅修改JwtDecoder的claim转换器,未配置JwtAuthenticationConverter
  2. 未指定authoritiesClaimName,导致Spring默认查找scope/scp字段而非authorities
  3. 不必要保留SCOPE_前缀,与令牌中已有的ROLE_前缀冲突

内容的提问来源于stack exchange,提问作者Hugo L.M

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 05:32:48