I am creating a simple SpringBoot application and trying to integrate with OAuth 2.0 provider Keycloak. I have created a realm, client, roles (Member, PremiumMember) at realm level and finally created users and assigned roles (Member, PremiumMember).
If I use SpringBoot Adapter provided by Keycloak https://www.keycloak.org/docs/latest/securing_apps/index.html#_spring_boot_adapter then when I successfully login and check the Authorities of the loggedin user I am able to see the assigned roles such as Member, PremiumMember.
Collection<? extends GrantedAuthority> authorities =
SecurityContextHolder.getContext().getAuthentication().getAuthorities();
But if I use generic SpringBoot Auth2 Client Config I am able to login but when I check the Authorities it always show only ROLE_USER, SCOPE_email,SCOPE_openid,SCOPE_profile and didn't include the roles I mapped (Member, PremiumMember).
My SpringBoot OAuth2 config:
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
application.properties
spring.security.oauth2.client.provider.spring-boot-thymeleaf-client.issuer-uri=http://localhost:8181/auth/realms/myrealm
spring.security.oauth2.client.registration.spring-boot-thymeleaf-client.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.spring-boot-thymeleaf-client.client-id=spring-boot-app
spring.security.oauth2.client.registration.spring-boot-thymeleaf-client.client-secret=XXXXXXXXXXXXXX
spring.security.oauth2.client.registration.spring-boot-thymeleaf-client.scope=openid,profile,roles
spring.security.oauth2.client.registration.spring-boot-thymeleaf-client.redirect-uri=http://localhost:8080/login/oauth2/code/spring-boot-app
I am using SpringBoot 2.5.5 and Keycloak 15.0.2.
Using this generic OAuth2.0 config approach (without using Keycloak SpringBootAdapter) is there a way to get the assigned roles?
By default, Spring Security generates a list of GrantedAuthority
using the values in the scope
or scp
claim and the SCOPE_
prefix.
Keycloak keeps the realm roles in a nested claim realm_access.roles
. You have two options to extract the roles and map them to a list of GrantedAuthority
.
OAuth2 Client
If your application is configured as an OAuth2 Client, then you can extract the roles from either the ID Token or the UserInfo endpoint. Keycloak includes the roles only in the Access Token, so you need to change the configuration to include them also in either the ID Token or the UserInfo endpoint (which is what I use in the following example). You can do so from the Keycloak Admin Console, going to Client Scopes > roles > Mappers > realm roles
Then, in your Spring Security configuration, define a GrantedAuthoritiesMapper
which extracts the roles from the UserInfo endpoint and maps them to GrantedAuthority
s. Here, I'll include how the specific bean should look like. A full example is available on my GitHub: https://github.com/ThomasVitale/spring-security-examples/tree/main/oauth2/login-user-authorities
@Bean
public GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak() {
return authorities -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
var authority = authorities.iterator().next();
boolean isOidc = authority instanceof OidcUserAuthority;
if (isOidc) {
var oidcUserAuthority = (OidcUserAuthority) authority;
var userInfo = oidcUserAuthority.getUserInfo();
if (userInfo.hasClaim("realm_access")) {
var realmAccess = userInfo.getClaimAsMap("realm_access");
var roles = (Collection<String>) realmAccess.get("roles");
mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
}
} else {
var oauth2UserAuthority = (OAuth2UserAuthority) authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
if (userAttributes.containsKey("realm_access")) {
var realmAccess = (Map<String,Object>) userAttributes.get("realm_access");
var roles = (Collection<String>) realmAccess.get("roles");
mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
}
}
return mappedAuthorities;
};
}
Collection<GrantedAuthority> generateAuthoritiesFromClaim(Collection<String> roles) {
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
}
OAuth2 Resource Server
If your application is configured as an OAuth2 Resource Server, then you can extract the roles from the Access Token. In your Spring Security configuration, define a JwtAuthenticationConverter
bean which extracts the roles from the Access Token and maps them to GrantedAuthority
s. Here, I'll include how the specific bean should look like. A full example is available on my GitHub: https://github.com/ThomasVitale/spring-security-examples/tree/main/oauth2/resource-server-jwt-authorities
public JwtAuthenticationConverter jwtAuthenticationConverterForKeycloak() {
Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter = jwt -> {
Map<String, Collection<String>> realmAccess = jwt.getClaim("realm_access");
Collection<String> roles = realmAccess.get("roles");
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
};
var jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}