diff --git a/src/main/java/org/gcube/common/keycloak/DefaultKeycloakClient.java b/src/main/java/org/gcube/common/keycloak/DefaultKeycloakClient.java
index e065d7a..2596e07 100644
--- a/src/main/java/org/gcube/common/keycloak/DefaultKeycloakClient.java
+++ b/src/main/java/org/gcube/common/keycloak/DefaultKeycloakClient.java
@@ -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 {
diff --git a/src/main/java/org/gcube/common/keycloak/KeycloakClient.java b/src/main/java/org/gcube/common/keycloak/KeycloakClient.java
index 5f2b4f3..082cc8a 100644
--- a/src/main/java/org/gcube/common/keycloak/KeycloakClient.java
+++ b/src/main/java/org/gcube/common/keycloak/KeycloakClient.java
@@ -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 JWK
endpoint {@link URL} from the realm's base URL.
+ *
+ * @param realmBaseURL the realm's base URL to use
+ * @return the Keycloak JWK
endpoint URL
+ * @throws KeycloakClientException if something goes wrong discovering the endpoint URL
+ */
+ URL getJWKEndpointURL(URL realmBaseURL) throws KeycloakClientException;
+
/**
* Constructs the Keycloak introspection
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 public_key
, token-service
URL,
+ * Gets the realm info setup (RSA public_key
, token-service
URL,
* account-service
URL and tokens-not-before
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.
*
diff --git a/src/main/java/org/gcube/common/keycloak/model/JSONWebKeySet.java b/src/main/java/org/gcube/common/keycloak/model/JSONWebKeySet.java
new file mode 100644
index 0000000..1142ad3
--- /dev/null
+++ b/src/main/java/org/gcube/common/keycloak/model/JSONWebKeySet.java
@@ -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 Stian Thorgersen
+ */
+public class JSONWebKeySet {
+
+ @JsonProperty("keys")
+ private JWK[] keys;
+
+ public JWK[] getKeys() {
+ return keys;
+ }
+
+ public void setKeys(JWK[] keys) {
+ this.keys = keys;
+ }
+
+}
diff --git a/src/main/java/org/gcube/common/keycloak/model/JWK.java b/src/main/java/org/gcube/common/keycloak/model/JWK.java
new file mode 100644
index 0000000..5d92295
--- /dev/null
+++ b/src/main/java/org/gcube/common/keycloak/model/JWK.java
@@ -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 Stian Thorgersen
+ */
+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 otherClaims = new HashMap();
+
+
+ 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 getOtherClaims() {
+ return otherClaims;
+ }
+
+ @JsonAnySetter
+ public void setOtherClaims(String name, Object value) {
+ otherClaims.put(name, value);
+ }
+
+}
diff --git a/src/test/java/org/gcube/common/keycloak/TestKeycloakClient.java b/src/test/java/org/gcube/common/keycloak/TestKeycloakClient.java
index 480d7cc..97e172f 100644
--- a/src/test/java/org/gcube/common/keycloak/TestKeycloakClient.java
+++ b/src/test/java/org/gcube/common/keycloak/TestKeycloakClient.java
@@ -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