Search code examples
springspring-bootspring-securitykeycloak

Keycloak with Google login and role based access not working


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: enter image description here

Tried to create realm roles: enter image description here

Tried to make composite roles of both client and realm roles: enter image description here

Tried to add a mapper to the identity provider: enter image description here

And last I tried to add the role directly to the user: enter image description here

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


Solution

  • 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.

    enter image description here

    Then you click in realm roles:

    enter image description here

    Then you will enable everything like shown in the image:

    enter image description here

    Then go to realm roles in the left menu and create the user and admin roles.

    enter image description here

    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):

    enter image description here

    Here you will assign the roles that you want, in my case i want all new registred users to have the role user:

    enter image description here

    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:

    enter image description here

    enter image description here

    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