Skip to content

Instantly share code, notes, and snippets.

@thomasdarimont
Last active January 14, 2024 10:30
Show Gist options
  • Star 45 You must be signed in to star a gist
  • Fork 17 You must be signed in to fork a gist
  • Save thomasdarimont/52152ed68486c65b50a04fcf7bd9bbde to your computer and use it in GitHub Desktop.
Save thomasdarimont/52152ed68486c65b50a04fcf7bd9bbde to your computer and use it in GitHub Desktop.
Retrieve and verify AccessToken with Keycloak Client.
package de.tdlabs.keycloak.client;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.keycloak.OAuth2Constants;
import org.keycloak.RSATokenVerifier;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.KeycloakBuilder;
import org.keycloak.common.VerificationException;
import org.keycloak.jose.jws.JWSHeader;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.KeysMetadataRepresentation.KeyMetadataRepresentation;
import java.math.BigInteger;
import java.net.URL;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.List;
import java.util.Map;
public class KeycloakClientAuthExample {
public static void main(String[] args) {
KeycloakClientFacade facade = new KeycloakClientFacade( //
"http://localhost:8081/auth" //
, "admin-client-demo" //
, "demo-client-1" //
, "da6947c2-6559-4c37-b219-d37bb72ec2fa" //
);
// Get raw AccessToken string for client credentials grant
System.out.println(facade.getAccessTokenString());
// Get decoded AccessToken for client credentials grant
// System.out.println(facade.getAccessToken());
// Get decoded AccessToken for password credentials grant
AccessToken accessToken = facade.getAccessToken("tester", "test");
System.out.println(accessToken.getSubject());
}
static class KeycloakClientFacade {
private final String serverUrl;
private final String realmId;
private final String clientId;
private final String clientSecret;
public KeycloakClientFacade(String serverUrl, String realmId, String clientId, String clientSecret) {
this.serverUrl = serverUrl;
this.realmId = realmId;
this.clientId = clientId;
this.clientSecret = clientSecret;
}
public AccessToken getAccessToken() {
return getAccessToken(newKeycloakBuilderWithClientCredentials().build());
}
public String getAccessTokenString() {
return getAccessTokenString(newKeycloakBuilderWithClientCredentials().build());
}
public AccessToken getAccessToken(String username, String password) {
return getAccessToken(newKeycloakBuilderWithPasswordCredentials(username, password).build());
}
public String getAccessTokenString(String username, String password) {
return getAccessTokenString(newKeycloakBuilderWithPasswordCredentials(username, password).build());
}
private AccessToken getAccessToken(Keycloak keycloak) {
return extractAccessTokenFrom(keycloak, getAccessTokenString(keycloak));
}
private String getAccessTokenString(Keycloak keycloak) {
AccessTokenResponse tokenResponse = getAccessTokenResponse(keycloak);
return tokenResponse == null ? null : tokenResponse.getToken();
}
private AccessToken extractAccessTokenFrom(Keycloak keycloak, String token) {
if (token == null) {
return null;
}
try {
RSATokenVerifier verifier = RSATokenVerifier.create(token);
PublicKey publicKey = getRealmPublicKey(keycloak, verifier.getHeader());
return verifier.realmUrl(getRealmUrl()) //
.publicKey(publicKey) //
.verify() //
.getToken();
} catch (VerificationException e) {
return null;
}
}
private KeycloakBuilder newKeycloakBuilderWithPasswordCredentials(String username, String password) {
return newKeycloakBuilderWithClientCredentials() //
.username(username) //
.password(password) //
.grantType(OAuth2Constants.PASSWORD);
}
private KeycloakBuilder newKeycloakBuilderWithClientCredentials() {
return KeycloakBuilder.builder() //
.realm(realmId) //
.serverUrl(serverUrl)//
.clientId(clientId) //
.clientSecret(clientSecret) //
.grantType(OAuth2Constants.CLIENT_CREDENTIALS);
}
private AccessTokenResponse getAccessTokenResponse(Keycloak keycloak) {
try {
return keycloak.tokenManager().getAccessToken();
} catch (Exception ex) {
return null;
}
}
public String getRealmUrl() {
return serverUrl + "/realms/" + realmId;
}
public String getRealmCertsUrl() {
return getRealmUrl() + "/protocol/openid-connect/certs";
}
private PublicKey getRealmPublicKey(Keycloak keycloak, JWSHeader jwsHeader) {
// Variant 1: use openid-connect /certs endpoint
return retrievePublicKeyFromCertsEndpoint(jwsHeader);
// Variant 2: use the Public Key referenced by the "kid" in the JWSHeader
// in order to access realm public key we need at least realm role... e.g. view-realm
// return retrieveActivePublicKeyFromKeysEndpoint(keycloak, jwsHeader);
// Variant 3: use the active RSA Public Key exported by the PublicRealmResource representation
// return retrieveActivePublicKeyFromPublicRealmEndpoint();
}
private PublicKey retrievePublicKeyFromCertsEndpoint(JWSHeader jwsHeader) {
try {
ObjectMapper om = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> certInfos = om.readValue(new URL(getRealmCertsUrl()).openStream(), Map.class);
List<Map<String, Object>> keys = (List<Map<String, Object>>) certInfos.get("keys");
Map<String, Object> keyInfo = null;
for (Map<String, Object> key : keys) {
String kid = (String) key.get("kid");
if (jwsHeader.getKeyId().equals(kid)) {
keyInfo = key;
break;
}
}
if (keyInfo == null) {
return null;
}
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
String modulusBase64 = (String) keyInfo.get("n");
String exponentBase64 = (String) keyInfo.get("e");
// see org.keycloak.jose.jwk.JWKBuilder#rs256
Decoder urlDecoder = Base64.getUrlDecoder();
BigInteger modulus = new BigInteger(1, urlDecoder.decode(modulusBase64));
BigInteger publicExponent = new BigInteger(1, urlDecoder.decode(exponentBase64));
return keyFactory.generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private PublicKey retrieveActivePublicKeyFromPublicRealmEndpoint() {
try {
ObjectMapper om = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> realmInfo = om.readValue(new URL(getRealmUrl()).openStream(), Map.class);
return toPublicKey((String) realmInfo.get("public_key"));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private PublicKey retrieveActivePublicKeyFromKeysEndpoint(Keycloak keycloak, JWSHeader jwsHeader) {
List<KeyMetadataRepresentation> keys = keycloak.realm(realmId).keys().getKeyMetadata().getKeys();
String publicKeyString = null;
for (KeyMetadataRepresentation key : keys) {
if (key.getKid().equals(jwsHeader.getKeyId())) {
publicKeyString = key.getPublicKey();
break;
}
}
return toPublicKey(publicKeyString);
}
public PublicKey toPublicKey(String publicKeyString) {
try {
byte[] publicBytes = Base64.getDecoder().decode(publicKeyString);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
return null;
}
}
}
}
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.tdlabs.training</groupId>
<artifactId>idm-keycloak-client-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<keycloak.version>2.5.1.Final</keycloak.version>
<resteasy.version>3.0.14.Final</resteasy.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
<version>${resteasy.version}</version>
</dependency>
</dependencies>
</project>
@ChristianLutz
Copy link

Hello Thomas,

could you please add some licence information. So we know if we may use this code or not.

kind regards

@haglo
Copy link

haglo commented Mar 11, 2019

RSATokenVerifier is deprecated.
Could you please offer this code instead with AccessToken,
org.keycloak.representations.AccessToken

@zineounalli
Copy link

Hi
please can you describe and tell me about your code !
i want understand it
Thanks

@augi
Copy link

augi commented Apr 17, 2019

Also please note that Public Key can be easily read in this way:

val om = new ObjectMapper()
val jwks = om.readValue(new URL(getRealmCertsUrl()).openStream(), classOf[JSONWebKeySet])
val jwk = jwks.getKeys()(0)
val publicKey = JWKParser.create(jwk).toPublicKey

@srinivaskancharla
Copy link

@augi,
val om = new ObjectMapper(), what does val means here?

@augi
Copy link

augi commented Oct 19, 2020

@srinivaskancharla It is a class from Jackson library, it serves for JSON deserialization.

@srinivaskancharla
Copy link

@augi, could you please give me the full package details to import/download.

@mkronberger
Copy link

Hi Thomas,

thx for your example. If have two questions, which i hope you can answer.

  1. Why you have included resteasy-client in your pom? As far as i have seen, its not used.
  2. How can i use KeycloakRestTemplate now, to calling an other service with the received token.

Thx in advanced.

br
Michael

@galthran
Copy link

galthran commented Apr 28, 2021

Thanks for the code

I've improved two methods

private PublicKey retrievePublicKeyFromCertsEndpoint() {

        try {
            ObjectMapper om = new ObjectMapper();
            JSONWebKeySet jwks = om.readValue(new URL(getRealmCertsUrl()).openStream(), JSONWebKeySet.class);
            JWK jwk = jwks.getKeys()[0];
            return JWKParser.create(jwk).toPublicKey();
        } catch (Exception e) {
            log.error("Exception", e);
        }

        return null;
    }

and

private AccessToken extractAccessTokenFrom(String token) {

        if (token == null) {
            return null;
        }

        try {
            PublicKey publicKey = getRealmPublicKey();
            TokenVerifier tokenVerifier = TokenVerifier.create(token, AccessToken.class);
            return (AccessToken) tokenVerifier.publicKey(publicKey).verify().getToken();
        } catch (VerificationException e) {
            log.debug("VerificationException: ", e);
            return null;
        }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment