Keycloak refuses to return roles on request

333 views
Skip to first unread message

Ben Pracht

unread,
Jun 21, 2022, 1:36:37 AM6/21/22
to Keycloak User

My Keycloak is returning an OAuth2AuthenticationToken, but refuses to add the roles the user has. Instead, it's returning the somewhat generic:
```
Authority: ROLE_USER
Authority: SCOPE_email
Authority: SCOPE_openid
Authority: SCOPE_profile
```
In Postman, I was able to convince Keycloak to return a JWT token using the Get Token functionality. Inside the JWT, after decompiling, I saw all the roles I wanted it to see. Yet somehow, the Spring Boot configuration decided to shorten this down to something much smaller. I have no idea why it lops off the roles. I followed the example incantations on a half dozen web tutorials, but somehow missed the part where we tell Keycloak to return the roles.

I was hoping it would take my realm role and use that as a role. I annotated my controller with a @RolesAllowed. Attached is SecurityConfiguration to show how I attempted to define authorization and authentication and a snippet from the WorkQueue controller class.

SecurityConfiguration.java:
```
package com.mycompany.myapp.configurations;

import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;



// configure in extends WebSecurityConfigurerAdapter
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@KeycloakConfiguration
@Import(KeycloakSpringBootConfigResolver.class)
public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {

    /**
     * Registers the KeycloakAuthenticationProvider with the authentication manager.
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = new KeycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
       
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }
   
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
        .antMatchers(
                "/favicon.ico",
                "/robots.txt",
                "/manifest.webmanifest", "/sw.js", "/offline.html",
                "/icons/**", "/images/**", "/styles/**", "/css/**", "/js/**",
                "/sw-runtime-resources-precache.js");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .authorizeRequests()
                .antMatchers("/work_queue/**").permitAll()//hasRole("myapp")
                .antMatchers("/actuator/**").permitAll()
                .anyRequest().authenticated().and().oauth2Login()
                .and()
                .logout()
                .invalidateHttpSession(true)
                .clearAuthentication(true)
                .logoutSuccessUrl("/login?logout")
                .deleteCookies("JSESSIONID")
                .permitAll()
                .and()
                .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
    }

    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(buildSessionRegistry());
    }

    @Bean
    protected SessionRegistry buildSessionRegistry() {
        return new SessionRegistryImpl();
    }
}

```

Snippet from WorkQueueController.java
```
@Controller
@RequestMapping("/work_queue")
public class WorkQueueController
    @GetMapping
    @RolesAllowed("my-role")  // Line A
    public String work_queue(Principal principal, Model model) {
        log.debug("Principal is a "+principal.getClass().getName());
        OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) principal;
        Object principal2 = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
        for(GrantedAuthority authority : authorities) {
            log.debug("Authority: "+authority.getAuthority());
        }
       
        log.debug("Principal2 is a "+principal2.getClass().getName());        
        DefaultOidcUser user = null;
        if(user!=null && user instanceof DefaultOidcUser) {
            user = (DefaultOidcUser) principal2;
        }

//        log.debug("Roles "+simpleKeycloakAccount.getRoles());
        Object principal3 = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if(principal3 != null && principal3 instanceof DefaultOidcUser) {
            DefaultOidcUser user3 = (DefaultOidcUser)principal3;
//            user = (DefaultOidcUser) principal;
            for(String key : user3.getAttributes().keySet()) {
                Object value = user3.getAttributes().get(key);
                log.debug("user3 key={} value={}",key,value);
            }
   
        }

    }
```

When Line A is present, I get a 403 (forbidden). When I comment line A, the method executes, and I'm able to see values I defined in Keycloak:
```
Principal is a org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken
Authority: ROLE_USER
Authority: SCOPE_email
Authority: SCOPE_openid
Authority: SCOPE_profile
Principal2 is a org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser
user3 key=at_hash value=<some-hash-value>
user3 key=sub value=<some-guid>
user3 key=email_verified value=true
user3 key=iss value=https://auth.mycompanytest.com/auth/realms/MyRealmk
user3 key=typ value=ID
user3 key=preferred_username value=<myemailaddress>
user3 key=given_name value=Woods
user3 key=nonce value=CZfW09_IKZbF5e0QhL-cDC4Uvk0QIJvmMqys0VmczlQ
user3 key=aud value=[myapp-app]
user3 key=acr value=0
user3 key=azp value=myapp-app
user3 key=auth_time value=2022-06-20T22:56:52Z
user3 key=name value=Woods Man
user3 key=exp value=2022-06-20T23:11:44Z
user3 key=session_state value=aa3e95e1-47e5-463b-a599-bf486a40fc00
user3 key=family_name value=Man
user3 key=iat value=2022-06-20T23:06:44Z
user3 key=email value=<my-email-address>
user3 key=jti value=d0f1b89f-ee20-424f-b12e-1ea869f5e712
```

So, I conclude that it reached Keycloak, but it decided to *not* use or fetch Keycloak roles. Why?

Bonus points for telling me if how a role defined in a client works differently than a role defined in a realm.

<note: for privacy reasons, various things above hidden to protect the innocent>

Thanks,


Reply all
Reply to author
Forward
0 new messages