Spring Boot JWT角色权限失效:访问/users/all报403 Forbidden
问题:JWT身份认证下ADMIN角色访问
/users/all返回403 Forbidden 我正在开发个人应用的后端REST API,配置了基于JWT的身份认证,包含ADMIN和USER两种角色,目标是仅允许ADMIN角色访问/users/all接口,但用Insomnia请求localhost:7777/users/all时返回403 Forbidden错误,找不到原因。
生成的JWT
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJrZW50MSIsInJvbGVzIjoiQURNSU4iLCJleHAiOjE2NjExNTUxNDUyOTUsImlhdCI6MTY2MDA3NTE0NTI5NX0.RGJzJVkM6bB0g6YlK6FFMzbjjTZ8qPqGf9pfHMeKHfyDV_OM9lF1w8SDzys3SC-iNdBMgXMjVP972URcISwJ_A
相关接口控制器
@RestController @RequestMapping("/users") public class AppUserController { @RolesAllowed("ADMIN") @GetMapping("/all") public ResponseEntity<List<AppUserListDto>> getAllAppUsers() { logger.info("GET /users/all"); return new ResponseEntity<List<AppUserListDto>>(appUserServiceImpl.getAllUsers(), HttpStatus.OK); } }
安全配置
@EnableWebSecurity @Configuration @EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) public class SecurityConfiguration { @Autowired RestAuthenticationEntryPoint restAuthenticationEntryPoint; @Autowired JwtFilter jwtFilter; @Bean SecurityFilterChain web(HttpSecurity http) throws Exception { http.csrf().disable().exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() .antMatchers("/login").permitAll() .antMatchers("/users/add").permitAll() .antMatchers("/users/all").hasRole("ADMIN") .anyRequest().authenticated(); http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public RoleHierarchy roleHierarchy() { RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); String hierarchy = "ADMIN > USER"; roleHierarchy.setHierarchy(hierarchy); return roleHierarchy; } @Bean public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() { DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler(); expressionHandler.setRoleHierarchy(roleHierarchy()); return expressionHandler; } }
用户详情服务
@Service public class MyUserDetailService implements UserDetailsService { @Autowired AppUserRepository appUserRepository; @Autowired RoleRepository roleRepository; @Override public UserDetails loadUserByUsername(String appUsername) throws UsernameNotFoundException { AppUserEntity appUser = appUserRepository.findByAppUsername(appUsername); if (appUser == null) { return new org.springframework.security.core.userdetails.User( " ", " ", true, true, true, true, getAuthorities(Arrays.asList( roleRepository.findByRoleName("USER")))); } return new org.springframework.security.core.userdetails.User( appUser.getAppUsername(), appUser.getPassword(), true, true, true, true, getAuthorities(appUser.getRoles())); } private Collection<? extends GrantedAuthority> getAuthorities( Collection<Role> roles) { return getGrantedAuthorities(getPrivileges(roles)); } private List<String> getPrivileges(Collection<Role> roles) { List<String> privileges = new ArrayList<>(); List<Privilege> collection = new ArrayList<>(); for (Role role : roles) { privileges.add(role.getRoleName()); collection.addAll(role.getPrivileges()); } for (Privilege item : collection) { privileges.add(item.getName()); } return privileges; } private List<GrantedAuthority> getGrantedAuthorities(List<String> privileges) { List<GrantedAuthority> authorities = new ArrayList<>(); for (String privilege : privileges) { authorities.add(new SimpleGrantedAuthority(privilege)); } return authorities; } }
JWT工具类
@Component public class JwtUtils { @Autowired AppUserRepository appUserRepository; long JWT_VALIDITY = 5 * 60 * 60 * 60; @Value("${jwt.secret}") String secret; private final Logger logger = LoggerFactory.getLogger(JwtUtils.class); public String generateToken(Authentication authentication) { Map<String, Object> claims = new HashMap<>(); claims.put("roles",authentication.getAuthorities().stream().map(role -> role.getAuthority()).findFirst().orElseThrow(NoSuchElementException::new)); claims.put("iat",new Date(System.currentTimeMillis())); claims.put("exp", new Date(System.currentTimeMillis() + JWT_VALIDITY * 1000)); claims.put("sub", authentication.getName()); Map<String, Object> headerJwt = new HashMap<>(); headerJwt.put("alg", "HS512"); headerJwt.put("typ", "JWT"); //TODO Later : Header, claims, jwt... will be Base64urlEncoded return Jwts.builder() .setHeader(headerJwt) .setClaims(claims) .signWith(SignatureAlgorithm.HS512, secret).compact(); } public Authentication getAuthentication(String token) { Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); AppUserEntity appUser = appUserRepository.findByAppUsername(claims.getSubject()); logger.info("THIS IS THE SUBJECT FROM CLAIMS : {}", claims.getSubject()); Collection<? extends GrantedAuthority> authorities = getAuthorities(appUser.getRoles()); User principal = new User(claims.getSubject(), "", authorities); return new UsernamePasswordAuthenticationToken(principal, token, authorities); } private Collection<? extends GrantedAuthority> getAuthorities( Collection<Role> roles) { return getGrantedAuthorities(getPrivileges(roles)); } private List<String> getPrivileges(Collection<Role> roles) { List<String> privileges = new ArrayList<>(); List<Privilege> collection = new ArrayList<>(); for (Role role : roles) { privileges.add(role.getRoleName()); collection.addAll(role.getPrivileges()); } for (Privilege item : collection) { privileges.add(item.getName()); } return privileges; } private List<GrantedAuthority> getGrantedAuthorities(List<String> privileges) { List<GrantedAuthority> authorities = new ArrayList<>(); for (String privilege : privileges) { authorities.add(new SimpleGrantedAuthority(privilege)); } return authorities; } }
JWT控制器
@RestController public class JwtController { @Autowired JwtUtils jwtUtils; @Autowired AuthenticationManagerBuilder authenticationManagerBuilder; @PostMapping("/login") public ResponseEntity<?> createAuthToken(@RequestBody JwtRequest jwtRequest) { Authentication authentication = logUser(jwtRequest.getAppUsername(), jwtRequest.getPassword()); String jwt = jwtUtils.generateToken(authentication); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.add(AUTHORIZATION_HEADER, "Bearer " + jwt); Object principal = authentication.getPrincipal(); return new ResponseEntity<>(new JwtResponse(((User) principal).getUsername()), httpHeaders, HttpStatus.OK); } public Authentication logUser(String appUsername, String password) { Authentication authentication = authenticationManagerBuilder.getObject() .authenticate(new UsernamePasswordAuthenticationToken(appUsername, password)); SecurityContextHolder.getContext().setAuthentication(authentication); return authentication; } }
JWT过滤器
@Component public class JwtFilter extends OncePerRequestFilter { @Autowired JwtUtils jwtUtils; public static final String AUTHORIZATION_HEADER = "Authorization"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String jwt = resolveToken(request); if (StringUtils.hasText(jwt)) { Authentication authentication = jwtUtils.getAuthentication(jwt); SecurityContextHolder.getContext().setAuthentication(authentication); } chain.doFilter(request, response); } private String resolveToken(HttpServletRequest request) { String bearerToken = request.getHeader(AUTHORIZATION_HEADER); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } }
问题原因及解决方案
核心问题:Spring Security角色前缀规则不匹配
Spring Security中,hasRole("ADMIN")方法会自动给角色名添加ROLE_前缀,即实际检查的是ROLE_ADMIN权限,但你的代码中给用户赋予的权限是ADMIN(没有前缀),导致权限校验不通过,返回403。同时@RolesAllowed("ADMIN")遵循JSR-250规范,不会自动添加前缀,因此注解本身是正确的,但HttpSecurity中的规则与实际权限冲突。
解决方案(二选一即可)
方案1:统一添加角色前缀
修改MyUserDetailService和JwtUtils中的getPrivileges方法,将角色名添加ROLE_前缀:
// 替换原代码中的privileges.add(role.getRoleName()); privileges.add("ROLE_" + role.getRoleName());
修改后JWT中的roles字段会变为ROLE_ADMIN,与hasRole("ADMIN")的校验规则匹配。
方案2:关闭Spring Security的角色前缀
在SecurityConfiguration中添加Bean,关闭自动添加前缀的行为:
@Bean public GrantedAuthorityDefaults grantedAuthorityDefaults() { return new GrantedAuthorityDefaults(""); // 移除默认前缀 }
这样hasRole("ADMIN")会直接检查ADMIN权限,与你当前的权限命名一致。
额外检查点
- 确认
JwtFilter正确解析请求头中的Token,并将Authentication对象设置到SecurityContext中 - 可在
JwtUtils.getAuthentication方法中打印authorities内容,验证权限是否正确加载
内容的提问来源于stack exchange,提问作者Quentin Genet
相关产品推荐
相关产品推荐

