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

Spring Boot 3 Security配置问题:@PreAuthorize无法识别角色

问题:Spring Security @PreAuthorize无法识别Token中的角色

当前项目基于新版Spring Security(弃用WebSecurityConfigurerAdapter)开发,用户认证(User实现UserDetails,Hibernate持久化)和Token生成功能正常,登录可获取200响应及有效Token,且角色信息已写入Token。但控制器添加@PreAuthorize注解(如@PreAuthorize("hasAnyRole('USER', 'ADMIN')"))后请求被禁止,移除注解则Token验证通过。

问题根源

  1. 角色前缀不匹配:Spring Security的hasAnyRole()默认会给角色添加ROLE_前缀,但你的Token中存储的角色是USER/ADMIN,无前缀导致权限匹配失败。
  2. JWT权限解析缺失配置:默认情况下,Spring Security不会自动将Token中的scope声明转换为GrantedAuthority,需自定义转换器完成映射。

解决方案

步骤1:统一角色前缀(推荐)

在构建用户权限时添加ROLE_前缀,与hasAnyRole()的默认行为对齐:
修改UserDetailsImpl.build()方法:

public static UserDetailsImpl build(AuthUser authUser) {
    List<GrantedAuthority> authorities = new ArrayList<>();
    // 为角色添加ROLE_前缀
    authorities.add(new SimpleGrantedAuthority("ROLE_" + authUser.getRol().toString()));
    
    return new UserDetailsImpl(
            authUser.getId(),
            authUser.getUsername(),
            authUser.getRiziv(),
            authUser.getPassword(),
            authorities, authUser.isVerified());
}

Token生成逻辑无需额外修改,因为authentication.getAuthorities()已包含前缀,直接收集即可。

步骤2:配置JWT权限转换器

添加自定义JwtAuthenticationConverter,告诉Spring Security如何从Token中提取权限:
在SecurityConfig中新增Bean:

@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
    JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
    // 指定Token中存储权限的Claim字段名(此处为"scope")
    grantedAuthoritiesConverter.setAuthoritiesClaimName("scope");
    // 默认前缀为ROLE_,与步骤1的配置匹配,可省略
    // grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");

    JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
    converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
    return converter;
}

然后在securityFilterChain中替换原oauth2配置:

.oauth2ResourceServer(oauth2 -> oauth2
        .jwt(jwt -> jwt
                .jwtAuthenticationConverter(jwtAuthenticationConverter())
        )
)

步骤3:验证控制器注解

保持原注解写法即可,hasAnyRole()会自动补全ROLE_前缀:

@PreAuthorize("hasAnyRole('USER', 'ADMIN')")

也可以直接使用hasAnyAuthority指定完整权限名:

@PreAuthorize("hasAnyAuthority('ROLE_USER', 'ROLE_ADMIN')")

若不想添加ROLE_前缀,可修改转换器前缀为空,同时改用hasAnyAuthority:

grantedAuthoritiesConverter.setAuthorityPrefix("");

控制器注解对应改为:

@PreAuthorize("hasAnyAuthority('USER', 'ADMIN')")

完整修改后的SecurityConfig示例

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
    private RSAKey rsaKey;
    private final DefaultUserDetailsService defaultUserDetailsService;
    
    public SecurityConfig(DefaultUserDetailsService defaultUserDetailsService) {
        this.defaultUserDetailsService = defaultUserDetailsService;
    }
    
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        
    return http 
               .cors(Customizer.withDefaults())
               .csrf(AbstractHttpConfigurer::disable)
               .authorizeHttpRequests(auth -> auth
                   .requestMatchers("/auth/**").permitAll()
                   .anyRequest().authenticated()
               )            
               .userDetailsService(defaultUserDetailsService)
               .sessionManagement(session ->  session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
               .oauth2ResourceServer(oauth2 -> oauth2
                       .jwt(jwt -> jwt
                               .jwtAuthenticationConverter(jwtAuthenticationConverter())
                       )
               )
               .headers(headers -> headers
                   .frameOptions().sameOrigin()
               )
               .httpBasic(withDefaults())
               .build();
    }
    
    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        rsaKey = Jwks.generateRsa();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }
    
    @Bean
    JwtDecoder jwtDecoder() throws JOSEException {
        return NimbusJwtDecoder.withPublicKey(rsaKey.toRSAPublicKey()).build();
   }
    
    @Bean
    JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwks) {
        return new NimbusJwtEncoder(jwks);
    }
    
    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
        
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(List.of("http://localhost:4200"));
        configuration.setAllowedMethods(List.of("GET","POST","DELETE"));
        configuration.setAllowedHeaders(List.of("Authorization","Content-Type"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",configuration);
        return source;
    }

    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        grantedAuthoritiesConverter.setAuthoritiesClaimName("scope");
        
        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return converter;
    }

}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 21:40:59