@ preauthorize有角色与requestMatchers(). hasrole()



我已经创建了依赖于Spring Resource Server的jwt实现。下面是配置类:

@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
@EnableMethodSecurity
public class WebSecurityConfig {
@Value("${app.chat.jwt.public.key}")
private RSAPublicKey publicKey;
@Value("${app.chat.jwt.private.key}")
private RSAPrivateKey privateKey;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.exceptionHandling(
exceptions ->
exceptions
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(new BearerTokenAccessDeniedHandler()));
http.authorizeHttpRequests()
.requestMatchers("/auth/sign-in").permitAll()
.requestMatchers("/auth/sign-up").permitAll()
//                .requestMatchers("/hello").hasRole("ROLE_USER")
.anyRequest().authenticated()
.and()
.httpBasic(Customizer.withDefaults())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
@Bean
public AuthenticationManager authManager(UserDetailsServiceImpl userDetailsService) {
var authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return new ProviderManager(authProvider);
}
@SneakyThrows
@Bean
public JwtEncoder jwtEncoder() {
var jwk = new RSAKey.Builder(publicKey).privateKey(privateKey).build();
var jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
@SneakyThrows
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(publicKey).build();
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
var jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("scope");
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
var jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}

还有一个简单的控制器:

@RestController
public class HelloController {
@PreAuthorize("hasRole('ROLE_USER')")
@GetMapping("/hello")
public String hello() {
return "Hello!";
}
}

如果我使用@PreAuthorize("hasRole('ROLE_USER')"),我总是返回一个403状态码。但是如果我在配置文件中做同样的事情:requestMatchers("/hello").hasRole("ROLE_USER"),那么它就会按预期工作。我有@EnableMethodSecurity注释,所以@PreAuthorize应该工作,但由于某种原因,它没有。另外,为什么我需要在hasRole()方法中添加ROLE_?我想Spring应该会帮我处理的。

主要问题是为什么hasRole()适用于requestMatchers()而不适用@PreAuthorize

如果需要,这里是gitHub repo的链接(不要注意提交消息):https://github.com/EternalSadnes/chat-app

更新:

以下是@PreAuthorize注释请求/hello端点失败后的日志:

2022-12-05T12:27:45.300+02:00 DEBUG 6260 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : Securing GET /hello
2022-12-05T12:27:45.312+02:00 DEBUG 6260 --- [nio-8080-exec-2] o.s.s.o.s.r.a.JwtAuthenticationProvider  : Authenticated token
2022-12-05T12:27:45.313+02:00 DEBUG 6260 --- [nio-8080-exec-2] .s.r.w.a.BearerTokenAuthenticationFilter : Set SecurityContextHolder to JwtAuthenticationToken [Principal=org.springframework.security.oauth2.jwt.Jwt@621ae24b, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_ROLE_USER]]
2022-12-05T12:27:45.315+02:00 DEBUG 6260 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : Secured GET /hello
2022-12-05T12:27:45.317+02:00 DEBUG 6260 --- [nio-8080-exec-2] horizationManagerBeforeMethodInterceptor : Authorizing method invocation ReflectiveMethodInvocation: public java.lang.String com.eternal.chatapp.controller.HelloController.hello(); target is of class [com.eternal.chatapp.controller.HelloController]
2022-12-05T12:27:45.347+02:00 DEBUG 6260 --- [nio-8080-exec-2] horizationManagerBeforeMethodInterceptor : Failed to authorize ReflectiveMethodInvocation: public java.lang.String com.eternal.chatapp.controller.HelloController.hello(); target is of class [com.eternal.chatapp.controller.HelloController] with authorization manager org.springframework.security.config.annotation.method.configuration.DeferringObservationAuthorizationManager@e30a265 and decision ExpressionAuthorizationDecision [granted=false, expressionAttribute=hasRole('ROLE_USER')]

关于我如何调试它更深或者我应该注意什么,有什么想法吗?

确实,安全调试日志很有帮助。从它们中,我看到spring在某些时候将ROLE_添加到授予的权限中,并导致ROLE_ROLE_USER。我能够通过在没有ROLE_前缀的令牌中添加角色来解决这个问题。

String[] scope = user.getAuthorities().stream()
.map(grantedAuthority -> StringUtils.substringAfter(grantedAuthority.getAuthority(),"ROLE_"))
.toArray(String[]::new);

我在某个地方看到我们可以阻止Spring添加角色前缀,但是我丢失了这篇文章。

最新更新