Spring Security hasAnyRole失效,请求返回403 Forbidden问题求助
解决Spring Security中.hasAnyRole规则失效问题
问题现象
- 已注册
TRAINER角色的用户,通过POST请求http://localhost:8082/sign/in成功获取JWT令牌 - 携带该Bearer令牌发送GET请求
http://localhost:8082/trainer/test时,返回403 Forbidden状态码
控制台日志
o.s.s.w.access.AccessDeniedHandlerImpl [0;39m [2m:[0;39m Responding with 403 status code o.s.security.web.FilterChainProxy [0;39m [2m:[0;39m Securing GET /error o.s.s.w.a.AnonymousAuthenticationFilter [0;39m [2m:[0;39m Set SecurityContextHolder to anonymous SecurityContext o.s.s.w.a.Http403ForbiddenEntryPoint [0;39m [2m:[0;39m Pre-authenticated entry point called. Rejecting access
核心代码片段
SecurityConfiguration类
@Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfiguration { private final JwtAuthenticationFilter jwtAuthFilter; private final AuthenticationProvider authenticationProvider; private final LogoutHandler logoutHandler; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf() .disable() .authorizeHttpRequests() .requestMatchers("/sign/**") .permitAll() .requestMatchers("/admin/**").hasAnyRole(Role.ADMIN.name()) .requestMatchers("/trainer/**").hasAnyRole(Role.TRAINER.name()) .anyRequest() .authenticated() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authenticationProvider(authenticationProvider) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) .logout() .logoutUrl("/logout") .addLogoutHandler(logoutHandler) .logoutSuccessHandler( (request, response, authentication) -> SecurityContextHolder.clearContext()); return http.build(); } }
JwtService类(关键部分)
@Service public class JwtService { // ...其他代码 public String generateToken(UserDetailsImpl userDetails) { Map<String, Object> extraClaims = new HashMap<>(); extraClaims.put("id :", userService.getRoleIdFromUserId(userDetails.getId())); extraClaims.put("role", userDetails.getAuthorities().stream().findFirst().get().getAuthority()); return generateToken(extraClaims, userDetails); } // ...其他代码 }
Role枚举
public enum Role { ADMIN, TRAINER }
问题根源
Spring Security的hasAnyRole()方法会自动为角色名称添加ROLE_前缀进行匹配,但当前代码中:
- Role枚举定义的是
ADMIN/TRAINER,无ROLE_前缀 - UserDetailsImpl的
getAuthorities()返回的权限(或JWT中存储的角色)也没有ROLE_前缀 - 权限校验时,系统期望匹配
ROLE_TRAINER,但实际用户持有的是TRAINER,导致匹配失败返回403
解决方案
提供两种可行方案,任选其一即可:
方案1:统一添加ROLE_前缀
方式A:修改Role枚举
直接在枚举值中加入ROLE_前缀:
public enum Role { ROLE_ADMIN, ROLE_TRAINER }
方式B:在UserDetailsImpl中动态添加前缀
如果不想修改枚举,可在getAuthorities()方法中为角色添加前缀:
@Override public Collection<? extends GrantedAuthority> getAuthorities() { // 假设user是当前用户实体,role字段为Role枚举 return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().name())); }
同时确保JwtAuthenticationFilter解析JWT后,构造Authentication对象时使用带前缀的权限:
// JwtAuthenticationFilter的doFilterInternal方法中 String role = jwtService.extractClaim(token, claims -> claims.get("role", String.class)); // 如果用hasRole,这里要确保权限带ROLE_前缀 Collection<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority(role)); UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, authorities ); SecurityContextHolder.getContext().setAuthentication(authToken);
方案2:改用hasAnyAuthority()替代hasAnyRole()
hasAnyAuthority()不会自动添加前缀,直接使用原始角色名称匹配,修改SecurityConfiguration中的规则:
.requestMatchers("/admin/**").hasAnyAuthority(Role.ADMIN.name()) .requestMatchers("/trainer/**").hasAnyAuthority(Role.TRAINER.name())
此方案无需修改角色枚举或UserDetailsImpl,只需确保JWT中存储的角色和用户持有的权限与枚举名称一致即可。
内容的提问来源于stack exchange,提问作者user22437588
相关产品推荐
相关产品推荐

