I am having the same problem myself. One of the problems I am getting its getting copies of things like the JWT
tag i.e. the text that Keycloak
has encode you settings
@GetMapping("/whoami")
@ResponseBody
public Map<String, Object> index(
@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient,
Authentication auth) {
log.error("XXAuth is {}",auth);
log.error("XXClient is {}", authorizedClient.getClientRegistration());
log.error("XXClient access is {}", authorizedClient.getAccessToken());
log.error("Token {}",authorizedClient.getAccessToken().getTokenValue());
}
This code will get you some of the values that are part of the conversation, the Token part is the JWT
token, you can copy and paste that into jwt.io
and find out what what Keycloak
has actually sent.
This normally looks like
{
"exp": 1622299931,
"iat": 1622298731,
"auth_time": 1622298258,
"jti": "635ca59f-c87b-40da-b4ae-39774ed8098a",
"iss": "http://clunk:8080/auth/realms/spring-cloud-gateway-realm",
"sub": "6de0d95f-95b0-419d-87a4-b2862e8d0763",
"typ": "Bearer",
"azp": "spring-cloud-gateway-client",
"nonce": "2V8_3siQjTOIRbfs68BHwzvz3-dWeqXGUultzhJUWrA",
"session_state": "dd226823-90bc-429e-9cac-bb575b7d4fa0",
"acr": "0",
"realm_access": {
"roles": [
"ROLE_ANYONE"
]
},
"resource_access": {
"spring-cloud-gateway-client": {
"roles": [
"ROLE_ADMIN_CLIENT"
]
}
},
"scope": "openid email profile roles",
"email_verified": true,
"preferred_username": "anon"
}
As you can see Keycloak
supports two different types of ROLE tokens, but they are not defined in top level, but under realm_access
and resource_access
, the difference being resource access defines ROLE that are part of a resource and real_access defines roles that are defined across all realms.
To get these values defined, its necessary to define a Mapper, as follows 
To load these values in to Spring security you need to define a userAuthoritiesMapper
Bean and export the settings found in the attributes as SimpleGrantedAuthority
, as follows.
package foo.bar.com;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class RoleConfig {
@Bean
GrantedAuthoritiesMapper userAuthoritiesMapper() {
String ROLES_CLAIM = "roles";
return authorities -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
for (Object authority : authorities) {
boolean isOidc = authority instanceof OidcUserAuthority;
if (isOidc) {
log.error("Discovered an Oidc type of object");
var oidcUserAuthority = (OidcUserAuthority) authority;
java.util.Map<String, Object> attribMap = oidcUserAuthority.getAttributes();
JSONObject jsonClaim;
for (String attrib : attribMap.keySet()) {
log.error("Attribute name {} type {} ", attrib, attrib.getClass().getName());
Object claim = attribMap.get(attrib);
if (attrib.equals("realm_access")) {
log.error("Define on roles for entire client");
jsonClaim = (JSONObject) claim;
if (!jsonClaim.isEmpty()) {
log.error("JobClaim is {}", jsonClaim);
Object roleStr = jsonClaim.get("roles");
if (roleStr != null) {
log.error("Role String {}", roleStr.getClass().getName());
JSONArray theRoles = (JSONArray) roleStr; //jsonClaim.get("roles");
for (Object roleName : theRoles) {
log.error("Name {} ", roleName);
}
}
}
}
if (attrib.equals("resource_access")) {
log.error("Unique to attrib client");
jsonClaim = (JSONObject) claim;
if (!jsonClaim.isEmpty()) {
log.error("Job is {}", jsonClaim);
String clientName = jsonClaim.keySet().iterator().next();
log.error("Client name {}", clientName);
JSONObject roleObj = (JSONObject) jsonClaim.get(clientName);
Object roleNames = roleObj.get("roles");
log.error("Role names {}", roleNames.getClass().getName());
JSONArray theRoles = (JSONArray) roleObj.get("roles");
for (Object roleName : theRoles) {
log.error("Name {} ", roleName);
}
}
}
}
var userInfo = oidcUserAuthority.getUserInfo();
log.error("UserInfo {}", userInfo);
for (String key : userInfo.getClaims().keySet()) {
log.error("UserInfo keys {}", key);
}
if (userInfo.containsClaim(ROLES_CLAIM)) {
var roles = userInfo.getClaimAsStringList(ROLES_CLAIM);
mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
} else {
log.error("userInfo DID NOT FIND A claim");
}
} else {
var oauth2UserAuthority = (SimpleGrantedAuthority) authority;
log.error("Authority name " + authority.getClass().getName());
}
}
return mappedAuthorities;
};
}
private Collection<GrantedAuthority> generateAuthoritiesFromClaim(Collection<String> roles) {
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
}
}
Please note this code is based on a sample found at OAuth2 Login with custom granted authorities from UserInfo
The access to Attributes is my own work.
Note an error message will be generated at the highest level if no realm_access
or resource_access
is found, as I assume that wanting to decode a Keycloak
reference is the reason for using this code.
When working correctly, it generates the following output
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Discovered an Oidc type of object
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name at_hash type java.lang.String
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name sub type java.lang.String
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name resource_access type java.lang.String
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Unique to attrib client
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Job is {"spring-cloud-gateway-client":{"roles":["ROLE_ADMIN_CLIENT"]}}
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Client name spring-cloud-gateway-client
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Role names net.minidev.json.JSONArray
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Name ROLE_ADMIN_CLIENT
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name email_verified type java.lang.String
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name iss type java.lang.String
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name typ type java.lang.String
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name preferred_username type java.lang.String
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name nonce type java.lang.String
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name aud type java.lang.String
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name acr type java.lang.String
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name realm_access type java.lang.String
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Define on roles for entire client
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : JobClaim is {"roles":["ROLE_ANYONE"]}
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Role String net.minidev.json.JSONArray
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Name ROLE_ANYONE
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name azp type java.lang.String
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name auth_time type java.lang.String
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name exp type java.lang.String
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name session_state type java.lang.String
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name iat type java.lang.String
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Attribute name jti type java.lang.String
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : UserInfo org.springframework.security.oauth2.core.oidc.OidcUserInfo@8be9a0b8
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : UserInfo keys sub
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : UserInfo keys email_verified
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : UserInfo keys preferred_username
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : userInfo DID NOT FIND A claim
2021-05-29 15:32:11.252 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Authority name org.springframework.security.core.authority.SimpleGrantedAuthority
2021-05-29 15:32:11.252 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Authority name org.springframework.security.core.authority.SimpleGrantedAuthority
2021-05-29 15:32:11.252 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Authority name org.springframework.security.core.authority.SimpleGrantedAuthority
2021-05-29 15:32:11.252 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig : Authority name org.springframework.security.core.authority.SimpleGrantedAuthority
2021-05-29 15:32:11.252 DEBUG 7394 --- [or-http-epoll-5] o.s.w.r.f.client.ExchangeFunctions : [34ff3355] Cancel signal (to close connection)
2021-05-29 15:32:11.252 DEBUG 7394 --- [or-http-epoll-5] o.s.w.r.f.client.ExchangeFunctions : [1b083d68] Cancel signal (to close connection)
2021-05-29 15:32:11.254 DEBUG 7394 --- [or-http-epoll-5] ebSessionServerSecurityContextRepository : Saved SecurityContext 'SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=Name: [anon], Granted Authorities: [[ROLE_USER, SCOPE_email, SCOPE_openid, SCOPE_profile, SCOPE_roles]], User Attributes: [{at_hash=GCz2JybWiLc-42ACnjLJ6w, sub=6de0d95f-95b0-419d-87a4-b2862e8d0763, resource_access={"spring-cloud-gateway-client":{"roles":["ROLE_ADMIN_CLIENT"]}}, email_verified=true, iss=http://clunk:8080/auth/realms/spring-cloud-gateway-realm, typ=ID, preferred_username=anon, nonce=2V8_3siQjTOIRbfs68BHwzvz3-dWeqXGUultzhJUWrA, aud=[spring-cloud-gateway-client], acr=0, realm_access={"roles":["ROLE_ANYONE"]}, azp=spring-cloud-gateway-client, auth_time=2021-05-29T14:24:18Z, exp=2021-05-29T14:52:11Z, session_state=dd226823-90bc-429e-9cac-bb575b7d4fa0, iat=2021-05-29T14:32:11Z, jti=7d479a85-d76e-4930-9c86-b384a56d7af5}], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[]]]' in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession@69c3d462'