KEYCLOAK-6073 Make adapters use discovery endpoint for URLs instead of hardcoding (#6412)

This commit is contained in:
Stian Thorgersen 2019-11-06 10:34:36 +01:00 committed by Marek Posolda
parent 041229f9ca
commit f14f92ab0b
7 changed files with 100 additions and 14 deletions

View file

@ -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;
}

View file

@ -162,7 +162,6 @@ public class KeycloakDeploymentBuilder {
});
}
log.debug("Use authServerUrl: " + deployment.getAuthServerBaseUrl() + ", tokenUrl: " + deployment.getTokenUrl() + ", relativeUrls: " + deployment.getRelativeUrls());
return deployment;
}

View file

@ -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());
}
}

View file

@ -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;
}
}
}

View file

@ -97,6 +97,11 @@
<version>${cxf.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>

View file

@ -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";
}