KEYCLOAK-6073 Make adapters use discovery endpoint for URLs instead of hardcoding (#6412)
This commit is contained in:
parent
041229f9ca
commit
f14f92ab0b
7 changed files with 100 additions and 14 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -162,7 +162,6 @@ public class KeycloakDeploymentBuilder {
|
|||
});
|
||||
}
|
||||
|
||||
log.debug("Use authServerUrl: " + deployment.getAuthServerBaseUrl() + ", tokenUrl: " + deployment.getTokenUrl() + ", relativeUrls: " + deployment.getRelativeUrls());
|
||||
return deployment;
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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";
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue