As per title I am setting up webflux config like this
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class WebSecurityConfig {
String jwkSetUri = "http://localhost:8080/auth/realms/demo/protocol/openid-connect/certs";
@Bean
public ReactiveJwtDecoder jwtDecoder() {
return
ReactiveJwtDecoders.fromIssuerLocation("http://localhost:8080/auth/realms/demo");
}
@Bean
public SecurityWebFilterChain configure(ServerHttpSecurity http) {
http.authorizeExchange()
.oauth2Login()
.and()
.
...
.oauth2ResourceServer()
.jwt()
.jwkSetUri(jwkSetUri)
// .jwtDecoder(jwtDecoder())
.jwtAuthenticationConverter(grantedAuthoritiesExtractor());
return http.build();
}
@Bean
Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() {
GrantedAuthoritiesExtractor extractor = new GrantedAuthoritiesExtractor();
return new ReactiveJwtAuthenticationConverterAdapter(extractor);
}
static class GrantedAuthoritiesExtractor
extends JwtAuthenticationConverter {
public Collection<GrantedAuthority> extractAuthorities(Jwt jwt) {
Collection<?> authorities = (Collection<?>)
jwt.getClaims().getOrDefault("roles", Collections.emptyList());
return authorities.stream()
.map(Object::toString)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
}
with the following application.yml
security:
oauth2:
client:
provider:
keycloak-local:
issuer-uri: http://localhost:8080/auth/realms/demo
registration:
keycloak-local:
client-id: some-id
client-secret: ...
resourceserver:
jwt:
issuer-uri: http://localhost:8080/auth/realms/demo
jwk-set-uri: http://localhost:8080/auth/realms/demo/protocol/openid-
connect/certs
the token format is like that
"realm_access": {
"roles": [
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"someclient": {
"roles": [
"uma_protection"
]
},
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "profile email ",
"organizationId": "605b74813bd72c65a24a1704",
"accountId": "605b74813bd72c65a24a1709",
"email_verified": false,
"roles": [
"ROLE_ADMIN"
],
"preferred_username": "someUsername",
"userId": "605b74813bd72c65a24a1706",
"email": "email.com"
}`
and of course the "roles" array is the one that I would like ideally to access(is the role that I define in my DB).
After that setup it seems that nothing has changed . So in my controller I tried to use the @Preauthorize annotation to access to some method but it keeps working with the scope claim.
@PreAuthorize("hasAuthority('SCOPE_email')")
Does anyone knows what I am missing? Please note that I refer to official documentation but also to this tutorial. for webflux.
Thanks
By default, Spring Security converts the items in the scope
or scp
claim and uses the SCOPE_
prefix. If your application is a pure OAuth2 Resource Server, you can change both conventions by defining a custom ReactiveJwtAuthenticationConverter
bean.
For example, to export authorities from a roles
scope and using the `` prefix (since ROLE_
is already part of your role name), you can define the following bean. Spring Security will pick it up automatically, you don't need to reference it from your SecurityWebFilterChain configuration.
@Bean
public ReactiveJwtAuthenticationConverter jwtAuthenticationConverter() {
var jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
var jwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(
new ReactiveJwtGrantedAuthoritiesConverterAdapter(jwtGrantedAuthoritiesConverter));
return jwtAuthenticationConverter;
}
Since your application is also configured as an OAuth2 Client (with oauth2Login()
), then you need a different approach. You can either define a GrantedAuthoritiesMapper
or an OAuth2UserService
. Examples for both options can be found in the Spring Security documentation.