Spring Security中Authentication.getAuthorities()返回空集合问题
问题根源分析
你使用的是spring-boot-starter-oauth2-resource-server依赖,这套依赖的默认认证逻辑是直接解析JWT令牌获取用户信息,而非传统Spring Security中基于UserDetailsService从数据库加载用户权限的流程。你的UserServiceImpl里的loadUserByUsername方法根本没被执行,自然Authentication对象的权限集合是空的。
具体来说:
- 资源服务器场景下,Spring Security默认会创建
JwtAuthenticationToken作为认证对象,它的权限集合默认从JWT的scope/scp字段解析,而你的权限存在自定义的authorities字段中,默认不会被识别。 - 你手动从
Jwt对象中提取authorities字段能拿到值,是因为这只是直接读取JWT的原始内容,并非Spring Security认证流程中加载的权限。
解决方案
根据你的需求,可以选择两种配置方式:
方式一:直接从JWT的authorities字段解析权限
这种方式不需要调用UserDetailsService,直接让Spring Security从自定义JWT字段中提取权限,配置如下:
@Configuration @ConditionalOnProperty(name = "davinci.security.enabled", havingValue = "true") public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .jwtAuthenticationConverter(jwtAuthenticationConverter()) ) ) .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ); return http.build(); } private Converter<Jwt, AbstractAuthenticationToken> jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter(); // 指定从JWT的"authorities"字段读取权限 authoritiesConverter.setAuthoritiesClaimName("authorities"); // 给权限添加前缀,方便和@PreAuthorize("hasRole('ADMIN')")配合使用 authoritiesConverter.setAuthorityPrefix("ROLE_"); JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter(); jwtConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter); return jwtConverter; } // 补充:需要配置JWT解码器,示例为对称密钥场景,根据你的授权服务器实际情况调整 @Bean public JwtDecoder jwtDecoder() { return NimbusJwtDecoder.withSecretKey( new SecretKeySpec("your-jwt-secret-key".getBytes(), "HmacSHA256") ).build(); } }
配置完成后,authentication.getAuthorities()就能拿到转换后的GrantedAuthority(比如ROLE_ADMIN),你之前注释的权限判断代码就能正常工作。
方式二:结合UserDetailsService从数据库加载权限
如果需要从数据库动态获取用户权限(而非完全依赖JWT中的字段),可以配置让Spring Security在解析JWT后调用UserDetailsService加载权限:
@Configuration @ConditionalOnProperty(name = "davinci.security.enabled", havingValue = "true") public class SecurityConfig { private final UserDetailsService userDetailsService; public SecurityConfig(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .oauth2ResourceServer(oauth2 -> oauth2 .authenticationProvider(jwtAuthenticationProvider()) ) .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ); return http.build(); } @Bean public JwtAuthenticationProvider jwtAuthenticationProvider() { JwtAuthenticationProvider provider = new JwtAuthenticationProvider(jwtDecoder()); // 设置UserDetailsService,用于从数据库加载用户权限 provider.setUserDetailsService(userDetailsService); // 配置权限合并策略:合并JWT自带权限和数据库加载的权限 provider.setAuthoritiesMapper((jwtAuthorities, userDetails) -> { Collection<GrantedAuthority> combined = new ArrayList<>(jwtAuthorities); combined.addAll(userDetails.getAuthorities()); return combined; }); return provider; } @Bean public JwtDecoder jwtDecoder() { // 根据授权服务器配置调整解码器,示例为对称密钥场景 return NimbusJwtDecoder.withSecretKey( new SecretKeySpec("your-jwt-secret-key".getBytes(), "HmacSHA256") ).build(); } }
注:如果JWT中存储的用户名是email而非默认的sub字段,还需要自定义JwtAuthenticationConverter将email作为用户名传入UserDetailsService。
内容的提问来源于stack exchange,提问作者Paul Marcelin Bejan
相关产品推荐
相关产品推荐

