I am currently developing a demo prototype to secure a spring service through a Spring Gateway against a Keycloak. With my current configuration, everytime I access one of the mapped URLs in the gateway it redirects me to the keycloak' login screen and, after introducing my credentials, it redirects me to the service and the result is shown on screen. Now, I am trying to secure specific endpoints in the service using roles, so any user trying to access to them needs to have a specific role. In order to do so, I have added the "@EnableMethodSecurity" annotation to my configuration as follows:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()).oauth2ResourceServer((oauth2ResourceServer) -> oauth2ResourceServer.jwt((jwt) -> jwt.decoder(jwtDecoder())));
//OLD http.authorizeRequests(authorize -> authorize.anyRequest().authenticated()).oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri("http://[KEYCLOAK_IP]:8080/realms/my-realm/protocol/openid-connect/certs").build();
}
}
I have also added the "@PreAuthorize("hasRole('myrole')")" annotation to one of the endpoints of the service as follows:
@Controller
public class ExampleController {
@PreAuthorize("hasRole('myrole')")
@GetMapping("/securedexample")
@ResponseBody
public String getString(){
return "secured string";
}
@GetMapping("/getToken")
@ResponseBody
public String getString2(){
Jwt token =(Jwt)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return "Value of the token:*******" + token.getTokenValue() + "*******";
}
Everytime I try to access to the service secured with the @PreAuthorize annotation, no matter the user I am logged in, I get a blank screen in the browser and I can see the following in the log:
Failed to authorize ReflectiveMethodInvocation: public java.util.String com.webdemo.controllers.ExampleController.getString(); target is of class [com.webdemo.controllers.ExampleController] with authorization manager org.springframework.security.config.annotation.method.configuration.DeferringObservationAuthorizationManager@7828b138 and decision ExpressionAuthorizationDecision [granted=false, expressionAttribute=hasRole('myrole')]
It seems that it does not find the role in the logged user. I have 2 users on keycloak, each one with a different role (myrole and myrole2) and when I check the content of the JWS token of the logged user (the one with the correct role) I can see that it has the role. Next I paste the relevant part of the token:
...
"realm_access": {
"roles": [
"offline_access",
"uma_authorization",
"myrole", <-- The role is here!
"default-roles-my-realm"
]
},
...
I have search over internet and found several code variations to secure and endpoint:
@PreAuthorize("hasRole('myrole')")
@PreAuthorize("hasRole('ROLE_myrole')")
@PreAuthorize("hasAuthority('myrole')")
@PreAuthorize("hasAuthority('ROLE_myrole')")
I have tried all 4 variations with no luck. I have also searched and found this link PreAuthorize not working on Controller , but it seems to use the same code above. Any clue about what I am doing wrongly? Thanks in advance!