Spring Boot 3 Security配置问题:@PreAuthorize无法识别角色
当前项目基于新版Spring Security(弃用WebSecurityConfigurerAdapter)开发,用户认证(User实现UserDetails,Hibernate持久化)和Token生成功能正常,登录可获取200响应及有效Token,且角色信息已写入Token。但控制器添加@PreAuthorize注解(如@PreAuthorize("hasAnyRole('USER', 'ADMIN')"))后请求被禁止,移除注解则Token验证通过。
问题根源
- 角色前缀不匹配:Spring Security的
hasAnyRole()默认会给角色添加ROLE_前缀,但你的Token中存储的角色是USER/ADMIN,无前缀导致权限匹配失败。 - 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
相关产品推荐
相关产品推荐

