I have a Spring Boot application using keycloak, I'm giving the option to the user to login via google, right now the user is able to login via google and the token is being generated, but when I check the JWT I don't get the roles that I set to the user and in my Spring Boot application, the user is unable to access routes that must have user or admin permission. My goal is to create a user and admin role and make routes that can only be accessed if the user have such roles, I also want all user to have the role user by default and the admin role will need to be set manually.
I have tried a few different ways to create the roles but none worked. I'm not sure if I'm creating the roles wrong or there is another step to add when doing identity provider role based access. I appreaciate any help!
Create the role in the client:
Tried to make composite roles of both client and realm roles:
Tried to add a mapper to the identity provider:
And last I tried to add the role directly to the user:
Spring Boot application:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(
authorizeConfig -> {
authorizeConfig.requestMatchers("/public").permitAll();
authorizeConfig.requestMatchers("/logout").permitAll();
authorizeConfig.requestMatchers("/admin").hasRole("admin");
authorizeConfig.requestMatchers("/usuario").hasRole("user");
authorizeConfig.anyRequest().authenticated();
})
.oauth2Login(Customizer.withDefaults())
.oauth2ResourceServer(config -> {
config.jwt(Customizer.withDefaults());
})
.build();
}
}
My application.yml:
spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: my-keycloak-client-id
client-secret: my-keycloak-client-secret
scope: openid,profile,email
provider:
keycloak:
user-name-attribute: preferred_username
issuer-uri: http://localhost:8180/realms/google-teste
resourceserver:
jwt:
issuer-uri: http://localhost:8180/realms/google-teste
My Jwt (I've hidden sensitive information)
{
"iat": 1712089584,
"auth_time": 1712089583,
"jti": "REDACTED",
"iss": "http://localhost:8180/realms/google-teste",
"aud": "spring-security-keycloak",
"sub": "REDACTED",
"typ": "ID",
"azp": "spring-security-keycloak",
"nonce": "REDACTED",
"session_state": "REDACTED",
"at_hash": "REDACTED",
"acr": "1",
"sid": "REDACTED",
"email_verified": false,
"name": "REDACTED",
"preferred_username": "REDACTED",
"given_name": "REDACTED",
"family_name": "REDACTED",
"email": "REDACTED",
"realm_access": {
"roles": [
"offline_access",
"default-roles-google-teste",
"uma_authorization"
]
},
}
The route to access the jwt token
@GetMapping("/cookie")
String cookie(@AuthenticationPrincipal OidcUser principal) {
return String.format("""
<h1>Oauth2 🔐 </h1>
<h3>Principal: %s</h3>
<h3>Email attribute: %s</h3>
<h3>Authorities: %s</h3>
<h3>JWT: %s</h3>
""", principal, principal.getAttribute("email"), principal.getAuthorities(),
principal.getIdToken().getTokenValue());
}
Versions:
Keycloak: 24.0.2
Spring Boot: 3.1.5
I managed to find the solution to this.
First you need to go to client scopes on the left menu, then put the microprofile-jwt as default after that you click in the roles.
Then you click in realm roles:
Then you will enable everything like shown in the image:
Then go to realm roles in the left menu and create the user and admin roles.
After that you click in the "default-roles-my-realm"(in this example i created a realm called my-realm, so in your the name after the default-roles will change):
Here you will assign the roles that you want, in my case i want all new registred users to have the role user:
Now if you want to make a user an admin all you need to do is go to users in the left menu and click in the user, then go to the role mapping tab and assign the admin role to him:
This is how my spring boot application transform the roles user and admin to role_user and role_admin:
SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private static final String REALM_ACCESS_CLAIM = "realm_access";
private static final String ROLES_CLAIM = "roles";
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(
authorizeConfig -> {
authorizeConfig.requestMatchers("/").permitAll();
authorizeConfig.requestMatchers("/protected").hasRole("admin");
authorizeConfig.requestMatchers("/userinfo").hasRole("user");
authorizeConfig.anyRequest().authenticated();
})
.oauth2Login(Customizer.withDefaults())
.oauth2ResourceServer(config -> {
config.jwt(Customizer.withDefaults());
})
.build();
}
@Bean
@SuppressWarnings("unchecked")
// to check the user roles such as role_user or role_admin
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();
var userInfo = oidcUserAuthority.getIdToken();
if (userInfo.hasClaim(REALM_ACCESS_CLAIM)) {
var realmAccess = userInfo.getClaimAsMap(REALM_ACCESS_CLAIM);
var roles = (Collection<String>) realmAccess.get(ROLES_CLAIM);
mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
}
} else {
var oauth2UserAuthority = (OAuth2UserAuthority) authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
if (userAttributes.containsKey(REALM_ACCESS_CLAIM)) {
var realmAccess = (Map<String, Object>) userAttributes.get(REALM_ACCESS_CLAIM);
var roles = (Collection<String>) realmAccess.get(ROLES_CLAIM);
mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
}
}
return mappedAuthorities;
};
}
// transform the default keycloak role to role_ for example user to role_user
Collection<GrantedAuthority> generateAuthoritiesFromClaim(Collection<String> roles) {
return roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).collect(Collectors.toList());
}
}
I'm using this keycloak image version: quay.io/keycloak/keycloak:23.0.7
spring boot version 3.2.3