Spring Security基于角色访问控制返回403错误求助
Spring Security JWT授权403问题排查与解决
我正在开发一个集成Spring Security与JWT令牌授权的小型应用,遇到如下问题:使用hasRole()或hasPermission()方法区分请求访问权限时,即使授权用户拥有符合限制的角色,请求仍返回403状态码。已尝试添加ROLE_前缀、使用纯字符串格式定义角色、同时使用hasRole()和hasAuthority()方法,但问题依旧。之前按此方式实现时一切正常。
UserDetails与UserDetailsService实现
@RequiredArgsConstructor public class UserDetailsImpl implements UserDetails { private final User user; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return user.getRoles(); } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public boolean isAccountNonExpired() { return user.getIsEnable(); } @Override public boolean isAccountNonLocked() { return user.getIsEnable(); } @Override public boolean isCredentialsNonExpired() { return user.getIsEnable(); } @Override public boolean isEnabled() { return user.getIsEnable(); } }
@Service @RequiredArgsConstructor public class UserDetailsServiceImpl implements UserDetailsService { private final UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { String exceptionMsg = String.format("User with username '%s' not found", username); return new UserDetailsImpl(userRepository.findUserByUsername(username) .orElseThrow(() -> new UsernameNotFoundException(exceptionMsg))); } }
User实体与Roles枚举
@Entity(name = "users") @AllArgsConstructor @NoArgsConstructor @Getter @Setter @EqualsAndHashCode(of = {"id", "username"}) public class User { @Transient private final String MAIL_REGEX = "^\\w+([\\.-]?\\w+)*@\\w+([\\.-]?\\w+)*(\\.\\w{2,3})+$"; @Id @GeneratedValue(strategy = IDENTITY) private Long id; @Column(unique = true, nullable = false) @NotBlank(message = "Username cannot be empty") @NotNull(message = "Username cannot be empty") @Size(min = 1, max = 90) private String username; @Column(nullable = false) @NotBlank(message = "Password cannot be empty") @NotNull(message = "Password cannot be empty") @Size(min = 8, max = 100) private String password; @Column(unique = true, nullable = false) @Email(message = "Invalid email address", regexp = MAIL_REGEX) @NotBlank(message = "Email cannot be empty") private String email; @ElementCollection(fetch = EAGER) private Set<Role> roles = Set.of(Role.USER, Role.ADMIN, Role.SUPER_ADMIN); @Column(nullable = false) private Boolean isEnable = true; }
public enum Role implements GrantedAuthority { USER, ADMIN, SUPER_ADMIN; @Override public String getAuthority() { return name(); } }
安全配置
@Configuration public class SecurityConfig { @Value("${jwt.public.key}") RSAPublicKey publicKey; @Value("${jwt.private.key}") RSAPrivateKey privateKey; @Bean PasswordEncoder encoder() { return new BCryptPasswordEncoder(10); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf().disable(); http.authorizeHttpRequests().requestMatchers("/login", "/register").permitAll(); http.authorizeHttpRequests().requestMatchers("/hello").hasAuthority("USER"); http.httpBasic(Customizer.withDefaults()); http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); http.sessionManagement(session -> session.sessionCreationPolicy(STATELESS)); http.exceptionHandling() .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint()) .accessDeniedHandler(new BearerTokenAccessDeniedHandler()); return http.build(); } @Bean JwtDecoder jwtDecoder() { return NimbusJwtDecoder.withPublicKey(publicKey).build(); } @Bean JwtEncoder jwtEncoder() { JWK jwk = new RSAKey.Builder(publicKey).privateKey(privateKey).build(); JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk)); return new NimbusJwtEncoder(jwkSource); } }
问题排查与解决方案
核心问题在于JWT令牌未正确包含用户权限信息,或Spring Security未从JWT中正确解析权限。使用OAuth2 Resource Server的JWT模式时,Spring Security不会自动调用UserDetailsService加载权限,而是直接从JWT声明中提取。
1. 确保JWT生成时包含权限声明
在登录接口生成JWT时,必须将用户角色/权限写入令牌的自定义声明(如authorities):
@Autowired private JwtEncoder jwtEncoder; public String generateToken(UserDetails userDetails) { List<String> authorityStrings = userDetails.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .toList(); JwtClaimsSet claims = JwtClaimsSet.builder() .issuer("your-app") .issuedAt(Instant.now()) .expiresAt(Instant.now().plusMinutes(60)) .subject(userDetails.getUsername()) .claim("authorities", authorityStrings) // 关键:写入权限列表 .build(); return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); }
2. 配置Spring Security解析JWT权限
默认情况下,Spring Security从scope/scp声明提取权限并添加SCOPE_前缀。若权限存在authorities声明中,需自定义转换器:
@Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities"); // 指定权限声明名 grantedAuthoritiesConverter.setAuthorityPrefix(""); // 关闭默认前缀(若不需要) JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); return converter; }
在SecurityFilterChain中绑定转换器:
http.oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())));
3. 权限匹配验证
- 使用
hasAuthority("USER")时,确保JWT中权限字符串为USER(无前缀); - 使用
hasRole("USER")时,需权限字符串为ROLE_USER,可设置grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_"),或生成JWT时主动添加前缀。
4. 其他排查点
- 检查数据库中用户
roles集合是否存储正确,无大小写或格式错误; - 用JWT解析工具(如jwt.io)验证令牌中
authorities声明是否存在且内容正确; - 确认
UserDetailsImpl.getAuthorities()返回的角色集合非空且格式正确。
内容的提问来源于stack exchange,提问作者StrahSvid19
相关产品推荐
相关产品推荐

