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/MyRealmkuser3 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,