我正在使用 Oauth2 资源服务器 JWT 进行身份验证。 我正在尝试为两个路由添加身份验证,路由“/student/”以授予对角色 ROLE_USER 的访问权限,路由“/students/”对角色 ROLE_ADMIN 的访问权限。
安全配置
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception{
return httpSecurity
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(a ->
{
a.requestMatchers("/student/").hasAnyRole("ROLE_USER");
a.requestMatchers("/student/**").hasAnyRole("ROLE_USER");
a.requestMatchers("/students/").hasAnyRole("ROLE_ADMIN");
a.requestMatchers("/students/**").hasAnyRole("ROLE_ADMIN");
a.requestMatchers("/").permitAll();
a.requestMatchers("/token/").permitAll();
a.requestMatchers("/token/**").permitAll();
a.anyRequest().authenticated();
})
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.userDetailsService(userDetailsService)
.headers(headers -> headers.frameOptions().sameOrigin())
.httpBasic(Customizer.withDefaults())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.build();
}
我尝试将 hasRole()
更改为 hasAnyRole()
、 hasAuthority()
、 treid @PreAuthorize()
在 Controller 上,并使用不带“ROLE_
”前缀的角色进行检查,但没有任何效果。
用户详细信息服务
public class jpaUserDetailsService implements UserDetailsService {
UserRepository userRepository;
public jpaUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findUserByUsername(username)
.map(SecurityUser::new)
.orElseThrow(() -> new UsernameNotFoundException("Username not found " + username));
}
}
用户详细信息
public class SecurityUser implements UserDetails {
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getRoles().stream()
.map(Role::getRole)
.map(UserRoles::toString)
.map(SimpleGrantedAuthority::new).toList();
}
}
我对所有角色使用枚举
public enum UserRoles {
ROLE_ADMIN,
ROLE_USER
}
生成 jwt token 的服务
public class TokenService {
private final JwtEncoder jwtEncoder;
public TokenService(JwtEncoder jwtEncoder) {
this.jwtEncoder = jwtEncoder;
}
public String generateToken(Authentication authentication){
Instant now = Instant.now();
String scope = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "));
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer("self")
.issuedAt(now.plus(1 , ChronoUnit.HOURS))
.subject(authentication.getName())
.claim("scope" , scope)
.build();
return this.jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
}
用户实体
public class User {
@Id
@GeneratedValue
private long userId;
@Column(unique = true , nullable = false)
private String username;
@Column(nullable = false)
private String password;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
joinColumns = @JoinColumn(name = "userId"),
inverseJoinColumns = @JoinColumn(name = "roleId")
)
@ToString.Exclude
private List<Role> roles;
}
角色实体
public class Role {
@Id
@GeneratedValue
private Long roleId;
@Column(unique = true , nullable = false)
@Enumerated(EnumType.STRING)
private UserRoles role;
}
最后是我的 Controller
@GetMapping("/student/") // route i want to allow with role user;
public Student getStudent(Principal principal){
}
@GetMapping("/students/") // route i want to allow only role admin
public List<Student> getStudents(Principal principal){
}
}
最佳答案
默认的 JwtGrantedAuthoritiesConverter
实现从 scope
声明(您提供的)中读取权限,并在它们前面加上 SCOPE_
前缀。这意味着您的权限将变为 SCOPE_ROLE_USER
和 SCOPE_ROLE_ADMIN
。
您可以适应它并将 SecurityFilterChain
更改为:
a.requestMatchers("/student/").hasAnyAuthority("SCOPE_ROLE_USER");
或者您可以创建自定义JwtGrantedAuthoritiesConverter
:
@Bean
public JwtGrantedAuthoritiesConverter authoritiesConverter() {
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthorityPrefix(""); // Cannot be null
}
@Bean
public JwtAuthenticationConverter authenticationConverter(JwtGrantedAuthoritiesConverter authoritiesConverter) {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
return converter;
}
另请注意,权限和角色之间的区别在于角色只是遵循特定命名约定的权限(= 以 ROLE_
为前缀)。
这意味着您应该使用 hasAnyAuthority("ROLE_USER")
或 hasAnyRole("USER")
。
因此,如果禁用范围前缀,则应将 SecurityFilterChain
更改为:
a.requestMatchers("/student/").hasAnyRole("USER");
// or
a.requestMatchers("/student/").hasAnyAuthority("ROLE_USER");
在评论中您还询问了如何调试它。要调试它的行为是否正确,您可以:
- 解码您的 JWT token ,以验证您的
范围
声明包含您的原始权限。 - 在
JwtGrantedAuthoritiesConverter.getAuthorities()
方法中放置断点,以验证 token 是否正确读取。
关于java - Spring安全配置requestMatchers.hasRole()总是给出403禁止状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75525940/