Added support to JWK endpoint and key sets retrieve to take info about configured key algorithms

This commit is contained in:
Mauro Mugnaini 2024-04-30 18:31:07 +02:00
parent 7d98fbaa16
commit e339be5083
Signed by: mauro.mugnaini
GPG Key ID: 2440CFD0EB321EA8
5 changed files with 210 additions and 6 deletions

View File

@ -42,6 +42,7 @@ import org.gcube.common.gxhttp.util.ContentUtils;
import org.gcube.common.gxrest.request.GXHTTPStringRequest;
import org.gcube.common.gxrest.response.inbound.GXInboundResponse;
import org.gcube.common.gxrest.response.inbound.JsonUtils;
import org.gcube.common.keycloak.model.JSONWebKeySet;
import org.gcube.common.keycloak.model.ModelUtils;
import org.gcube.common.keycloak.model.PublishedRealmRepresentation;
import org.gcube.common.keycloak.model.TokenIntrospectionResponse;
@ -125,6 +126,23 @@ public class DefaultKeycloakClient implements KeycloakClient {
}
}
@Override
public URL getJWKEndpointURL(URL realmBaseURL) throws KeycloakClientException {
logger.debug("Constructing JWK endpoint URL starting from base URL: {}", realmBaseURL);
try {
URL jwkURL = null;
if (realmBaseURL.getPath().endsWith("/")) {
jwkURL = new URL(realmBaseURL, OPEN_ID_URI_PATH + "/" + JWK_URI_PATH);
} else {
jwkURL = new URL(realmBaseURL.toString() + "/" + OPEN_ID_URI_PATH + "/" + JWK_URI_PATH);
}
logger.debug("Constructed JWK URL is: {}", jwkURL);
return jwkURL;
} catch (MalformedURLException e) {
throw new KeycloakClientException("Cannot constructs JWK URL from base URL: " + realmBaseURL, e);
}
}
@Override
public URL getIntrospectionEndpointURL(URL realmBaseURL) throws KeycloakClientException {
logger.debug("Constructing introspection URL starting from base URL: {}", realmBaseURL);
@ -184,11 +202,21 @@ public class DefaultKeycloakClient implements KeycloakClient {
try {
return JsonUtils.fromJson(ContentUtils.toByteArray(realmURL.openStream()),
PublishedRealmRepresentation.class);
} catch (Exception e) {
throw new KeycloakClientException("Getting realm's info", e);
}
}
@Override
public JSONWebKeySet getRealmJSONWebKeySet(URL jwkURL) throws KeycloakClientException {
try {
return JsonUtils.fromJson(ContentUtils.toByteArray(jwkURL.openStream()), JSONWebKeySet.class);
} catch (Exception e) {
throw new KeycloakClientException("Getting realm's JWK", e);
}
}
@Override
public TokenResponse queryOIDCToken(String context, String clientId, String clientSecret)
throws KeycloakClientException {

View File

@ -4,7 +4,7 @@ import java.net.URL;
import java.util.List;
import java.util.Map;
import org.gcube.com.fasterxml.jackson.annotation.JsonProperty;
import org.gcube.common.keycloak.model.JSONWebKeySet;
import org.gcube.common.keycloak.model.PublishedRealmRepresentation;
import org.gcube.common.keycloak.model.TokenIntrospectionResponse;
import org.gcube.common.keycloak.model.TokenResponse;
@ -14,6 +14,7 @@ public interface KeycloakClient {
public static final String PROD_ROOT_SCOPE = "/d4science.research-infrastructures.eu";
public static final String OPEN_ID_URI_PATH = "protocol/openid-connect";
public static final String TOKEN_URI_PATH = "token";
public static final String JWK_URI_PATH = "certs";
public static final String TOKEN_INTROSPECT_URI_PATH = "introspect";
public static final String AVATAR_URI_PATH = "account-avatar";
public final static String D4S_CONTEXT_HEADER_NAME = "X-D4Science-Context";
@ -49,6 +50,15 @@ public interface KeycloakClient {
*/
URL getTokenEndpointURL(URL realmBaseURL) throws KeycloakClientException;
/**
* Constructs the Keycloak <code>JWK</code> endpoint {@link URL} from the realm's base URL.
*
* @param realmBaseURL the realm's base URL to use
* @return the Keycloak <code>JWK</code> endpoint URL
* @throws KeycloakClientException if something goes wrong discovering the endpoint URL
*/
URL getJWKEndpointURL(URL realmBaseURL) throws KeycloakClientException;
/**
* Constructs the Keycloak <code>introspection</code> endpoint {@link URL} from the realm's base URL.
*
@ -77,7 +87,7 @@ public interface KeycloakClient {
URL getAvatarEndpointURL(URL realmBaseURL) throws KeycloakClientException;
/**
* Get the realm info setup (RSA <code>public_key</code>, <code>token-service</code> URL,
* Gets the realm info setup (RSA <code>public_key</code>, <code>token-service</code> URL,
* <code>account-service</code> URL and <code>tokens-not-before</code> setting)
*
* @param realmURL the realm URL
@ -86,6 +96,8 @@ public interface KeycloakClient {
*/
PublishedRealmRepresentation getRealmInfo(URL realmURL) throws KeycloakClientException;
JSONWebKeySet getRealmJSONWebKeySet(URL jwkURL) throws KeycloakClientException;
/**
* Queries an OIDC token from the context's Keycloak server, by using provided clientId and client secret.
*

View File

@ -0,0 +1,38 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model;
import org.gcube.com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JSONWebKeySet {
@JsonProperty("keys")
private JWK[] keys;
public JWK[] getKeys() {
return keys;
}
public void setKeys(JWK[] keys) {
this.keys = keys;
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model;
import java.util.HashMap;
import java.util.Map;
import org.gcube.com.fasterxml.jackson.annotation.JsonAnyGetter;
import org.gcube.com.fasterxml.jackson.annotation.JsonAnySetter;
import org.gcube.com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JWK {
public static final String KEY_ID = "kid";
public static final String KEY_TYPE = "kty";
public static final String ALGORITHM = "alg";
public static final String PUBLIC_KEY_USE = "use";
public enum Use {
SIG("sig"),
ENCRYPTION("enc");
private String str;
Use(String str) {
this.str = str;
}
public String asString() {
return str;
}
}
@JsonProperty(KEY_ID)
private String keyId;
@JsonProperty(KEY_TYPE)
private String keyType;
@JsonProperty(ALGORITHM)
private String algorithm;
@JsonProperty(PUBLIC_KEY_USE)
private String publicKeyUse;
protected Map<String, Object> otherClaims = new HashMap<String, Object>();
public String getKeyId() {
return keyId;
}
public void setKeyId(String keyId) {
this.keyId = keyId;
}
public String getKeyType() {
return keyType;
}
public void setKeyType(String keyType) {
this.keyType = keyType;
}
public String getAlgorithm() {
return algorithm;
}
public void setAlgorithm(String algorithm) {
this.algorithm = algorithm;
}
public String getPublicKeyUse() {
return publicKeyUse;
}
public void setPublicKeyUse(String publicKeyUse) {
this.publicKeyUse = publicKeyUse;
}
@JsonAnyGetter
public Map<String, Object> getOtherClaims() {
return otherClaims;
}
@JsonAnySetter
public void setOtherClaims(String name, Object value) {
otherClaims.put(name, value);
}
}

View File

@ -5,6 +5,7 @@ import static org.junit.Assert.assertTrue;
import java.net.URL;
import java.util.Collections;
import org.gcube.common.keycloak.model.JSONWebKeySet;
import org.gcube.common.keycloak.model.ModelUtils;
import org.gcube.common.keycloak.model.PublishedRealmRepresentation;
import org.gcube.common.keycloak.model.TokenIntrospectionResponse;
@ -126,13 +127,26 @@ public class TestKeycloakClient {
}
@Test
public void test10QueryRealmInfo() throws Exception {
logger.info("*** [1.0] Start testing query realm info...");
public void test10aQueryRealmInfo() throws Exception {
logger.info("*** [1.0a] Start testing query realm info...");
KeycloakClient client = KeycloakClientFactory.newInstance();
PublishedRealmRepresentation realmInfo = client.getRealmInfo(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
logger.info("*** [1.0] Realm info public key PEM: {}", realmInfo.getPublicKeyPem());
logger.info("*** [1.0] Realm info public key: {}", realmInfo.getPublicKey());
logger.info("*** [1.0a] Realm info public key PEM: {}", realmInfo.getPublicKeyPem());
logger.info("*** [1.0a] Realm info public key: {}", realmInfo.getPublicKey());
}
@Test
public void test10bQueryRealmJWK() throws Exception {
logger.info("*** [1.0b] Start testing query realm JWK...");
KeycloakClient client = KeycloakClientFactory.newInstance();
JSONWebKeySet jsonWebKeySet = client
.getRealmJSONWebKeySet(client.getJWKEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT)));
for (int i = 0; i < jsonWebKeySet.getKeys().length; i++) {
logger.info("*** [1.0b] Realm JWK public key {} algorithm: {}", i,
jsonWebKeySet.getKeys()[i].getAlgorithm());
}
}
@Test