'Spring Boot Resource Server & Keycloak scope vs. role
is there anybody out there who is using Spring Boot Resource Server & Keycloak?
I configured my application.properties withspring.security.oauth2.resourceserver.jwt.issuer-uri = http://localhost:9080/auth/realms/<myrealm>
In my WebSecurityConfigurerAdapter i can use the client scopes like
.antMatchers(HttpMethod.GET, "/user/**").hasAuthority("SCOPE_read")
but i'm not able to use the roles!
.antMatchers(HttpMethod.GET, "/user/**").hasRole("ADMIN")
The information is availabe in the jwt, but spring does somehow not use it. Do anybody know where i can find a peace of documentation that describes the mapping? Somehow i think there is a node in my head, but where and which one ?
Thats my jwt:
"exp": 1603373908,
"iat": 1603373608,
"jti": "0b18b386-9f62-4c42-810e-692ccc4ed7d1",
"iss": "http://localhost:9080/auth/realms/jhipster",
"aud": "account",
"sub": "4c973896-5761-41fc-8217-07c5d13a004b",
"typ": "Bearer",
"azp": "web_app",
"session_state": "17411db5-8d50-4f25-b520-9a3e8b19fd67",
"acr": "1",
"allowed-origins": [
"*"
],
"realm_access": {
"roles": [
"test",
"ROLE_USER",
"offline_access",
"ROLE_ADMIN",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "email profile",
"email_verified": true,
"roles": [
"test",
"ROLE_USER",
"offline_access",
"ROLE_ADMIN",
"uma_authorization"
],
"name": "Admin Administrator",
"preferred_username": "admin",
"given_name": "Admin",
"family_name": "Administrator",
"email": "admin@localhost"
}
Thanks a lot Fredy
Solution 1:[1]
You need to define a Converter<Jwt, Collection<GrantedAuthority>>
that extracts the roles from Keycloak's realm_access
(null checks omitted for brevity):
public class KeycloakGrantedAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
@Override
public Collection<GrantedAuthority> convert(Jwt source) {
Map<String, Object> realmAccess = source.getClaimAsMap("realm_access");
List<String> roles = (List<String>) realmAccess.get("roles");
return roles.stream()
.map(rn -> new SimpleGrantedAuthority("ROLE_" + rn))
.collect(Collectors.toList());
}
}
Please note that this only extracts the Roles. If you need Scopes and Roles, check Spring's default JwtGrantedAuthoritiesConverter
for an code example.
Define it in a JwtAuthenticationConverter
:
private JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
jwtConverter.setJwtGrantedAuthoritiesConverter(new KeycloakGrantedAuthoritiesConverter());
return jwtConverter;
}
And finally use that in your WebSecurityConfigurerAdapter
:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// [...]
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
}
Solution 2:[2]
It could be due to the way Spring Security is parsing/extracting roles details from the JWT token. For example, in MicroProfile specification, it's mentioned that roles list will be extracted from "groups" property of the token.
Check RoleVoter documentation in Spring Security. Seems it expects role names to specifically start with the ROLE_ prefix. Maybe that's the case for you.
Solution 3:[3]
You have not included the versions used but I had the same problem with spring-security-oauth2-resource-server 5.2.2.RELEASE. This method:
org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter#getAuthorities
is responsible by default to get the authorities from the jwt. It only checks for the claim names scope
and scp
. However, as this response suggests, you can provide a CustomJwtAuthenticationConverter
and extract the roles as well.
Solution 4:[4]
In your security configuration define jwt authentication converter. You can set your claim name to be used by granted authorities converter.
Something like below, should work:
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
@Bean
public JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter() {
// You can use setAuthoritiesClaimName method
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
return jwtGrantedAuthoritiesConverter;
// You can also add your custom converter here
}
@Override
protected void configure(HttpSecurity http) throws Exception {
final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(this.jwtGrantedAuthoritiesConverter());
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter);
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | zaerymoghaddam |
Solution 3 | Bakk |
Solution 4 |