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

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权限,与你当前的权限命名一致。

额外检查点

  1. 确认JwtFilter正确解析请求头中的Token,并将Authentication对象设置到SecurityContext中
  2. 可在JwtUtils.getAuthentication方法中打印authorities内容,验证权限是否正确加载

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 21:39:17