java - Spring安全配置requestMatchers.hasRole()总是给出403禁止状态

标签 java spring spring-boot spring-security

我正在使用 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){
       
    }
}

enter image description here

最佳答案

默认的 JwtGrantedAuthoritiesConverter 实现从 scope 声明(您提供的)中读取权限,并在它们前面加上 SCOPE_ 前缀。这意味着您的权限将变为 SCOPE_ROLE_USERSCOPE_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/

相关文章:

spring - 如何在 JPA 中生成自定义 Id

java - Java版Random walk不收敛到期望值是什么原因?

spring - 无法从 tomcat 的 lib 文件夹访问自定义 jar

java - 如何从 jar 文件 @autowire 实现类?

multithreading - Spring 、JDBC 和多线程

spring-boot - Keycloak Kubernetes 401未经授权

spring-boot - 在 Springboot 2.0 中启用 Oauth2(Basic+password granttype),OAuth2LoginAuthenticationFilter class not found 错误

java - 从 war 文件中读取 list 文件会在 tomcat 7 中产生错误

java - 在空格上拆分字符串,但如果空格位于引号内则不拆分(引号部分可以类似于 xxx"x x x"xxx

java - Ubuntu 上的 Files.copy() 抛出 UnixException : No such file or directory even though the file exists