diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index e2bd3d74c0..0127decdb6 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -17,7 +17,9 @@
package org.keycloak.adapters;
+import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
import org.keycloak.adapters.authorization.PolicyEnforcer;
@@ -27,7 +29,9 @@ import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.enums.TokenStore;
+import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import org.keycloak.representations.adapters.config.AdapterConfig;
+import org.keycloak.util.JsonSerialization;
import java.net.URI;
import java.util.HashMap;
@@ -135,6 +139,17 @@ public class KeycloakDeployment {
this.authServerBaseUrl = config.getAuthServerUrl();
if (authServerBaseUrl == null) return;
+ authServerBaseUrl = KeycloakUriBuilder.fromUri(authServerBaseUrl).build().toString();
+
+ authUrl = null;
+ realmInfoUrl = null;
+ tokenUrl = null;
+ logoutUrl = null;
+ accountUrl = null;
+ registerNodeUrl = null;
+ unregisterNodeUrl = null;
+ jwksUrl = null;
+
URI authServerUri = URI.create(authServerBaseUrl);
if (authServerUri.getHost() == null) {
@@ -142,23 +157,49 @@ public class KeycloakDeployment {
} else {
// We have absolute URI in config
relativeUrls = RelativeUrlsUsed.NEVER;
- KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
- resolveUrls(serverBuilder);
}
}
-
-
/**
- * @param authUrlBuilder absolute URI
+ * URLs are loaded lazily when used. This allows adapter to be deployed prior to Keycloak server starting, and will
+ * also allow the adapter to retry loading config for each request until the Keycloak server is ready.
+ *
+ * In the future we may want to support reloading config at a configurable interval.
*/
+ protected void resolveUrls() {
+ if (realmInfoUrl == null) {
+ synchronized (this) {
+ KeycloakUriBuilder authUrlBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
+
+ String discoveryUrl = authUrlBuilder.clone().path(ServiceUrlConstants.DISCOVERY_URL).build(getRealm()).toString();
+ try {
+ log.debugv("Resolving URLs from {0}", discoveryUrl);
+
+ OIDCConfigurationRepresentation config = getOidcConfiguration(discoveryUrl);
+
+ authUrl = KeycloakUriBuilder.fromUri(config.getAuthorizationEndpoint());
+ realmInfoUrl = config.getIssuer();
+
+ tokenUrl = config.getTokenEndpoint();
+ logoutUrl = KeycloakUriBuilder.fromUri(config.getLogoutEndpoint());
+ accountUrl = KeycloakUriBuilder.fromUri(config.getIssuer()).path("/account").build().toString();
+ registerNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_REGISTER_NODE_PATH).build(getRealm()).toString();
+ unregisterNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH).build(getRealm()).toString();
+ jwksUrl = config.getJwksUri();
+
+ log.infov("Loaded URLs from {0}", discoveryUrl);
+ } catch (Exception e) {
+ log.warnv(e, "Failed to load URLs from {0}", discoveryUrl);
+ }
+ }
+ }
+ }
+
protected void resolveUrls(KeycloakUriBuilder authUrlBuilder) {
if (log.isDebugEnabled()) {
log.debug("resolveUrls");
}
- authServerBaseUrl = authUrlBuilder.build().toString();
-
String login = authUrlBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(getRealm()).toString();
authUrl = KeycloakUriBuilder.fromUri(login);
realmInfoUrl = authUrlBuilder.clone().path(ServiceUrlConstants.REALM_INFO_PATH).build(getRealm()).toString();
@@ -171,39 +212,59 @@ public class KeycloakDeployment {
jwksUrl = authUrlBuilder.clone().path(ServiceUrlConstants.JWKS_URL).build(getRealm()).toString();
}
+ protected OIDCConfigurationRepresentation getOidcConfiguration(String discoveryUrl) throws Exception {
+ HttpGet request = new HttpGet(discoveryUrl);
+ request.addHeader("accept", "application/json");
+
+ HttpResponse response = getClient().execute(request);
+ if (response.getStatusLine().getStatusCode() != 200) {
+ throw new Exception(response.getStatusLine().getReasonPhrase());
+ }
+
+ return JsonSerialization.readValue(response.getEntity().getContent(), OIDCConfigurationRepresentation.class);
+ }
+
public RelativeUrlsUsed getRelativeUrls() {
return relativeUrls;
}
public String getRealmInfoUrl() {
+ resolveUrls();
return realmInfoUrl;
}
public KeycloakUriBuilder getAuthUrl() {
+ resolveUrls();
return authUrl;
}
public String getTokenUrl() {
+ resolveUrls();
return tokenUrl;
}
public KeycloakUriBuilder getLogoutUrl() {
+ resolveUrls();
return logoutUrl;
}
public String getAccountUrl() {
+ resolveUrls();
return accountUrl;
}
public String getRegisterNodeUrl() {
+ resolveUrls();
return registerNodeUrl;
}
public String getUnregisterNodeUrl() {
+ resolveUrls();
return unregisterNodeUrl;
}
public String getJwksUrl() {
+ resolveUrls();
return jwksUrl;
}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index 416af5dbb9..b3a632f53e 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -162,7 +162,6 @@ public class KeycloakDeploymentBuilder {
});
}
- log.debug("Use authServerUrl: " + deployment.getAuthServerBaseUrl() + ", tokenUrl: " + deployment.getTokenUrl() + ", relativeUrls: " + deployment.getRelativeUrls());
return deployment;
}
diff --git a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
index 770876b31a..fa465807b4 100644
--- a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
+++ b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
@@ -28,6 +28,7 @@ import org.keycloak.common.enums.RelativeUrlsUsed;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.PemUtils;
import org.keycloak.enums.TokenStore;
+import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -50,7 +51,7 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"),
deployment.getPublicKeyLocator().getPublicKey(null, deployment));
- assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", deployment.getAuthUrl().build().toString());
+ assertEquals("https://localhost:8443/auth", deployment.getAuthServerBaseUrl());
assertEquals(SslRequired.EXTERNAL, deployment.getSslRequired());
assertTrue(deployment.isUseResourceRoleMappings());
assertTrue(deployment.isCors());
@@ -66,7 +67,6 @@ public class KeycloakDeploymentBuilderTest {
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
- assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/token", deployment.getTokenUrl());
assertEquals(RelativeUrlsUsed.NEVER, deployment.getRelativeUrls());
assertTrue(deployment.isAlwaysRefreshToken());
assertTrue(deployment.isRegisterNodeAtStartup());
@@ -101,4 +101,6 @@ public class KeycloakDeploymentBuilderTest {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-secret-jwt.json"));
assertEquals(JWTClientSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
}
+
+
}
diff --git a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentTest.java b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentTest.java
index ec7f74f9af..2d2c32e46b 100644
--- a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentTest.java
+++ b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentTest.java
@@ -17,6 +17,8 @@
package org.keycloak.adapters;
import org.junit.Test;
+import org.keycloak.common.util.KeycloakUriBuilder;
+import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import org.keycloak.representations.adapters.config.AdapterConfig;
import static org.junit.Assert.assertEquals;
@@ -30,31 +32,32 @@ import static org.junit.Assert.assertTrue;
public class KeycloakDeploymentTest {
@Test
public void shouldNotEnableOAuthQueryParamWhenIgnoreIsTrue() {
- KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
+ KeycloakDeployment keycloakDeployment = new KeycloakDeploymentMock();
keycloakDeployment.setIgnoreOAuthQueryParameter(true);
assertFalse(keycloakDeployment.isOAuthQueryParameterEnabled());
}
@Test
public void shouldEnableOAuthQueryParamWhenIgnoreIsFalse() {
- KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
+ KeycloakDeployment keycloakDeployment = new KeycloakDeploymentMock();
keycloakDeployment.setIgnoreOAuthQueryParameter(false);
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
}
@Test
public void shouldEnableOAuthQueryParamWhenIgnoreNotSet() {
- KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
+ KeycloakDeployment keycloakDeployment = new KeycloakDeploymentMock();
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
}
@Test
public void stripDefaultPorts() {
- KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
+ KeycloakDeployment keycloakDeployment = new KeycloakDeploymentMock();
keycloakDeployment.setRealm("test");
AdapterConfig config = new AdapterConfig();
config.setAuthServerUrl("http://localhost:80/auth");
+
keycloakDeployment.setAuthServerBaseUrl(config);
assertEquals("http://localhost/auth", keycloakDeployment.getAuthServerBaseUrl());
@@ -65,4 +68,19 @@ public class KeycloakDeploymentTest {
assertEquals("https://localhost/auth", keycloakDeployment.getAuthServerBaseUrl());
}
+ class KeycloakDeploymentMock extends KeycloakDeployment {
+
+ @Override
+ protected OIDCConfigurationRepresentation getOidcConfiguration(String discoveryUrl) throws Exception {
+ String base = KeycloakUriBuilder.fromUri(discoveryUrl).replacePath("/auth").build().toString();
+
+ OIDCConfigurationRepresentation rep = new OIDCConfigurationRepresentation();
+ rep.setAuthorizationEndpoint(base + "/realms/test/authz");
+ rep.setTokenEndpoint(base + "/realms/test/tokens");
+ rep.setIssuer(base + "/realms/test");
+ rep.setJwksUri(base + "/realms/test/jwks");
+ rep.setLogoutEndpoint(base + "/realms/test/logout");
+ return rep;
+ }
+ }
}
\ No newline at end of file
diff --git a/adapters/oidc/osgi-adapter/pom.xml b/adapters/oidc/osgi-adapter/pom.xml
index 4e35de6ba1..9b9fcbdff5 100755
--- a/adapters/oidc/osgi-adapter/pom.xml
+++ b/adapters/oidc/osgi-adapter/pom.xml
@@ -97,6 +97,11 @@
${cxf.version}
provided
+
+ org.apache.httpcomponents
+ httpclient
+ provided
+
junit
diff --git a/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java b/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java
index 36b4f1d2d6..b03a2e2ff3 100755
--- a/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java
+++ b/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java
@@ -31,5 +31,6 @@ public interface ServiceUrlConstants {
public static final String CLIENTS_MANAGEMENT_REGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/register-node";
public static final String CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/unregister-node";
public static final String JWKS_URL = "/realms/{realm-name}/protocol/openid-connect/certs";
+ public static final String DISCOVERY_URL = "/realms/{realm-name}/.well-known/openid-configuration";
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java b/core/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
similarity index 100%
rename from services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
rename to core/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java