Last active
August 8, 2020 01:50
-
-
Save imdadareeph/b339b7a21b1ace70dfe9ae9e827e9a87 to your computer and use it in GitHub Desktop.
springbootjwtauth2keycloak JwtAccessTokenCustomizer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.imdadareeph.springbootjwtauth2keycloak.config; | |
import com.fasterxml.jackson.databind.JsonNode; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.boot.autoconfigure.security.oauth2.resource.JwtAccessTokenConverterConfigurer; | |
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | |
import org.springframework.security.core.Authentication; | |
import org.springframework.security.core.GrantedAuthority; | |
import org.springframework.security.core.authority.AuthorityUtils; | |
import org.springframework.security.oauth2.provider.OAuth2Authentication; | |
import org.springframework.security.oauth2.provider.OAuth2Request; | |
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter; | |
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; | |
/** | |
* @author imdadareeph | |
* | |
* Spring oauth2 expects roles under authorities element in tokenMap, but keycloak provides it under resource_access. Hence extractAuthentication | |
* method is overriden to extract roles from resource_access. | |
* | |
* @return OAuth2Authentication with authorities for given application | |
*/ | |
public class JwtAccessTokenCustomizer extends DefaultAccessTokenConverter implements JwtAccessTokenConverterConfigurer { | |
private static final Logger LOG = LoggerFactory.getLogger(JwtAccessTokenCustomizer.class); | |
private static final String CLIENT_NAME_ELEMENT_IN_JWT = "resource_access"; | |
private static final String ROLE_ELEMENT_IN_JWT = "roles"; | |
private ObjectMapper mapper; | |
/* Using spring constructor injection, @Autowired is implicit */ | |
public JwtAccessTokenCustomizer(ObjectMapper mapper) { | |
this.mapper = mapper; | |
LOG.info("Initialized {}", JwtAccessTokenCustomizer.class.getSimpleName()); | |
} | |
@Override | |
public void configure(JwtAccessTokenConverter converter) { | |
converter.setAccessTokenConverter(this); | |
LOG.info("Configured {}", JwtAccessTokenConverter.class.getSimpleName()); | |
} | |
@Override | |
public OAuth2Authentication extractAuthentication(Map<String, ?> tokenMap) { | |
LOG.debug("Begin extractAuthentication: tokenMap = {}", tokenMap); | |
JsonNode token = mapper.convertValue(tokenMap, JsonNode.class); | |
Set<String> audienceList = extractClients(token); | |
List<GrantedAuthority> authorities = extractRoles(token); | |
OAuth2Authentication authentication = super.extractAuthentication(tokenMap); | |
OAuth2Request oAuth2Request = authentication.getOAuth2Request(); | |
OAuth2Request request = | |
new OAuth2Request(oAuth2Request.getRequestParameters(), oAuth2Request.getClientId(), authorities, true, oAuth2Request.getScope(), | |
audienceList, null, null, null); | |
Authentication usernamePasswordAuthentication = new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), "N/A", authorities); | |
LOG.debug("End extractAuthentication"); | |
return new OAuth2Authentication(request, usernamePasswordAuthentication); | |
} | |
private List<GrantedAuthority> extractRoles(JsonNode jwt) { | |
LOG.debug("Begin extractRoles: jwt = {}", jwt); | |
Set<String> rolesWithPrefix = new HashSet<>(); | |
jwt.path(CLIENT_NAME_ELEMENT_IN_JWT) | |
.elements() | |
.forEachRemaining(e -> e.path(ROLE_ELEMENT_IN_JWT) | |
.elements() | |
.forEachRemaining(r -> rolesWithPrefix.add("ROLE_" + r.asText()))); | |
final List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(rolesWithPrefix.toArray(new String[0])); | |
LOG.debug("End extractRoles: roles = {}", authorityList); | |
return authorityList; | |
} | |
private Set<String> extractClients(JsonNode jwt) { | |
LOG.debug("Begin extractClients: jwt = {}", jwt); | |
if (jwt.has(CLIENT_NAME_ELEMENT_IN_JWT)) { | |
JsonNode resourceAccessJsonNode = jwt.path(CLIENT_NAME_ELEMENT_IN_JWT); | |
final Set<String> clientNames = new HashSet<>(); | |
resourceAccessJsonNode.fieldNames() | |
.forEachRemaining(clientNames::add); | |
LOG.debug("End extractClients: clients = {}", clientNames); | |
return clientNames; | |
} else { | |
throw new IllegalArgumentException("Expected element " + CLIENT_NAME_ELEMENT_IN_JWT + " not found in token"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment