KEYCLOAK-2623 Remove auth-server-url-for-backend-requests from adapters

This commit is contained in:
mposolda 2016-04-05 10:39:03 +02:00
parent a4335c3eb8
commit 65dc7ddb44
19 changed files with 49 additions and 177 deletions

View file

@ -169,11 +169,7 @@ public class AdapterDeploymentContext {
public void setAuthServerBaseUrl(String authServerBaseUrl) {
this.authServerBaseUrl = authServerBaseUrl;
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
resolveBrowserUrls(serverBuilder);
if (delegate.getRelativeUrls() == RelativeUrlsUsed.ALL_REQUESTS) {
resolveNonBrowserUrls(serverBuilder);
}
resolveUrls(serverBuilder);
}
@Override

View file

@ -39,34 +39,14 @@ public class AdapterUtils {
}
/**
* Best effort to find origin for REST request calls from web UI application to REST application. In case of relative or absolute
* "auth-server-url" is returned the URL from request. In case of "auth-server-url-for-backend-request" used in configuration, it returns
* the origin of auth server.
*
* This may be the optimization in cluster, so if you have keycloak and applications on same host, the REST request doesn't need to
* go through loadbalancer, but can be sent directly to same host.
* Find origin for REST request calls from web UI application to REST application (assuming the REST application
* is deployed on same host like current UI application)
*
* @param browserRequestURL
* @param session
* @return
*/
public static String getOriginForRestCalls(String browserRequestURL, KeycloakSecurityContext session) {
if (session instanceof RefreshableKeycloakSecurityContext) {
KeycloakDeployment deployment = ((RefreshableKeycloakSecurityContext)session).getDeployment();
switch (deployment.getRelativeUrls()) {
case ALL_REQUESTS:
case NEVER:
// Resolve baseURI from the request
return UriUtils.getOrigin(browserRequestURL);
case BROWSER_ONLY:
// Resolve baseURI from the codeURL (This is already non-relative and based on our hostname)
return UriUtils.getOrigin(deployment.getTokenUrl());
default:
return "";
}
} else {
return UriUtils.getOrigin(browserRequestURL);
}
public static String getOriginForRestCalls(String browserRequestURL) {
return UriUtils.getOrigin(browserRequestURL);
}
public static Set<String> getRolesFromSecurityContext(RefreshableKeycloakSecurityContext session) {

View file

@ -111,39 +111,17 @@ public class KeycloakDeployment {
public void setAuthServerBaseUrl(AdapterConfig config) {
this.authServerBaseUrl = config.getAuthServerUrl();
String authServerURLForBackendReqs = config.getAuthServerUrlForBackendRequests();
if (authServerBaseUrl == null && authServerURLForBackendReqs == null) return;
if (authServerBaseUrl == null) return;
URI authServerUri = null;
if (authServerBaseUrl != null) {
authServerUri = URI.create(authServerBaseUrl);
}
URI authServerUri = URI.create(authServerBaseUrl);
if (authServerUri == null || authServerUri.getHost() == null) {
if (authServerURLForBackendReqs != null) {
relativeUrls = RelativeUrlsUsed.BROWSER_ONLY;
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerURLForBackendReqs);
if (serverBuilder.getHost() == null || serverBuilder.getScheme() == null) {
throw new IllegalStateException("Relative URL not supported for auth-server-url-for-backend-requests option. URL used: "
+ authServerURLForBackendReqs + ", Client: " + config.getResource());
}
resolveNonBrowserUrls(serverBuilder);
} else {
relativeUrls = RelativeUrlsUsed.ALL_REQUESTS;
}
if (authServerUri.getHost() == null) {
relativeUrls = RelativeUrlsUsed.ALWAYS;
} else {
// We have absolute URI in config
relativeUrls = RelativeUrlsUsed.NEVER;
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
resolveBrowserUrls(serverBuilder);
if (authServerURLForBackendReqs == null) {
resolveNonBrowserUrls(serverBuilder);
} else {
serverBuilder = KeycloakUriBuilder.fromUri(authServerURLForBackendReqs);
resolveNonBrowserUrls(serverBuilder);
}
resolveUrls(serverBuilder);
}
}
@ -152,23 +130,14 @@ public class KeycloakDeployment {
/**
* @param authUrlBuilder absolute URI
*/
protected void resolveBrowserUrls(KeycloakUriBuilder authUrlBuilder) {
protected void resolveUrls(KeycloakUriBuilder authUrlBuilder) {
if (log.isDebugEnabled()) {
log.debug("resolveBrowserUrls");
log.debug("resolveUrls");
}
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();
}
/**
* @param authUrlBuilder absolute URI
*/
protected void resolveNonBrowserUrls(KeycloakUriBuilder authUrlBuilder) {
if (log.isDebugEnabled()) {
log.debug("resolveNonBrowserUrls");
}
tokenUrl = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_PATH).build(getRealm()).toString();
logoutUrl = KeycloakUriBuilder.fromUri(authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH).build(getRealm()).toString());

View file

@ -54,7 +54,7 @@ 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://backend:8443/auth/realms/demo/protocol/openid-connect/token", deployment.getTokenUrl());
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/token", deployment.getTokenUrl());
assertEquals(RelativeUrlsUsed.NEVER, deployment.getRelativeUrls());
assertTrue(deployment.isAlwaysRefreshToken());
assertTrue(deployment.isRegisterNodeAtStartup());

View file

@ -24,7 +24,6 @@
"client-keystore": "classpath:/keystore.jks",
"client-keystore-password": "storepass",
"client-key-password": "keypass",
"auth-server-url-for-backend-requests": "https://backend:8443/auth",
"always-refresh-token": true,
"register-node-at-startup": true,
"register-node-period": 1000,

View file

@ -33,7 +33,7 @@ public class ServletOAuthClientBuilderTest {
public void testBuilder() {
ServletOAuthClient oauthClient = ServletOAuthClientBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
Assert.assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", oauthClient.getDeployment().getAuthUrl().clone().build().toString());
Assert.assertEquals("https://backend:8443/auth/realms/demo/protocol/openid-connect/token", oauthClient.getDeployment().getTokenUrl());
Assert.assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/token", oauthClient.getDeployment().getTokenUrl());
assertEquals(RelativeUrlsUsed.NEVER, oauthClient.getRelativeUrlsUsed());
Assert.assertEquals("customer-portal", oauthClient.getClientId());
Assert.assertEquals("234234-234234-234234", oauthClient.getCredentials().get(CredentialRepresentation.SECRET));

View file

@ -19,7 +19,6 @@
"connection-pool-size": 20,
"disable-trust-manager": true,
"allow-any-hostname": true,
"auth-server-url-for-backend-requests": "https://backend:8443/auth",
"always-refresh-token": true,
"register-node-at-startup": true,
"register-node-period": 1000,

View file

@ -25,29 +25,10 @@ public enum RelativeUrlsUsed {
/**
* Always use relative URI and resolve them later based on browser HTTP request
*/
ALL_REQUESTS,
/**
* Use relative Uris just for browser requests and resolve those based on browser HTTP requests.
* Backend request (like refresh token request, codeToToken request etc) will use the URI based on current hostname
*/
BROWSER_ONLY,
ALWAYS,
/**
* Relative Uri not used. Configuration contains absolute URI
*/
NEVER;
public boolean useRelative(boolean isBrowserReq) {
switch (this) {
case ALL_REQUESTS:
return true;
case NEVER:
return false;
case BROWSER_ONLY:
return isBrowserReq;
default:
return true;
}
}
}

View file

@ -34,7 +34,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
"connection-pool-size",
"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
"client-keystore", "client-keystore-password", "client-key-password",
"auth-server-url-for-backend-requests", "always-refresh-token",
"always-refresh-token",
"register-node-at-startup", "register-node-period", "token-store", "principal-attribute"
})
public class AdapterConfig extends BaseAdapterConfig {
@ -55,8 +55,6 @@ public class AdapterConfig extends BaseAdapterConfig {
protected String clientKeyPassword;
@JsonProperty("connection-pool-size")
protected int connectionPoolSize = 20;
@JsonProperty("auth-server-url-for-backend-requests")
protected String authServerUrlForBackendRequests;
@JsonProperty("always-refresh-token")
protected boolean alwaysRefreshToken = false;
@JsonProperty("register-node-at-startup")
@ -134,14 +132,6 @@ public class AdapterConfig extends BaseAdapterConfig {
this.connectionPoolSize = connectionPoolSize;
}
public String getAuthServerUrlForBackendRequests() {
return authServerUrlForBackendRequests;
}
public void setAuthServerUrlForBackendRequests(String authServerUrlForBackendRequests) {
this.authServerUrlForBackendRequests = authServerUrlForBackendRequests;
}
public boolean isAlwaysRefreshToken() {
return alwaysRefreshToken;
}

View file

@ -96,6 +96,23 @@
<section>
<title>Version specific migration</title>
<section>
<title>Migrating to 1.9.2</title>
<simplesect>
<title>Adapter option auth-server-url-for-backend-requests removed</title>
<para>
We've removed the option <literal>auth-server-url-for-backend-requests</literal> as there were issues in some scenarios when it was used.
In more details, it was possible to access the Keycloak server from 2 different contexts (internal and external), which was
causing issues in token validations etc.
</para>
<para>
If you still want to use the optimization of network, which this option provided (avoid the application to send backchannel requests
through loadbalancer but send them to local Keycloak server directly) you may need to handle it at hosts configuration (DNS) level.
</para>
</simplesect>
</section>
<section>
<title>Migrating to 1.9.0</title>
<simplesect>

View file

@ -102,39 +102,6 @@
</para>
</section>
<section id="relative-uri-optimization">
<title>Relative URI optimization</title>
<para>
In many deployment scenarios will be Keycloak and secured applications deployed on same cluster hosts. For this case Keycloak
already provides option to use relative URI as value of option <emphasis>auth-server-url</emphasis> in <literal>WEB-INF/keycloak.json</literal> .
In this case, the URI of Keycloak server is resolved from the URI of current request.
</para>
<para>
For example if your loadbalancer is on <emphasis>https://loadbalancer.com/myapp</emphasis> and auth-server-url is <emphasis>/auth</emphasis>,
then relative URI of Keycloak is resolved to be <emphasis>https://loadbalancer.com/auth</emphasis> .
</para>
<para>
For cluster setup, it may be even better to use option <emphasis>auth-server-url-for-backend-request</emphasis> . This allows to configure
that backend requests between Keycloak and your application will be sent directly to same cluster host without additional
round-trip through loadbalancer. So for this, it's good to configure values in <literal>WEB-INF/keycloak.json</literal> like this:
<programlisting>
<![CDATA[
"auth-server-url": "/auth",
"auth-server-url-for-backend-requests": "http://${jboss.host.name}:8080/auth"
]]>
</programlisting>
</para>
<para>
This would mean that browser requests (like redirecting to Keycloak login screen) will be still resolved relatively
to current request URI like <emphasis>https://loadbalancer.com/myapp</emphasis>, but backend (out-of-bound) requests between keycloak
and your app are sent always to same cluster host with application .
</para>
<para>
Note that additionally to network optimization,
you may not need "https" in this case as application and keycloak are communicating directly within same cluster host.
</para>
</section>
<section id="admin-url-configuration">
<title>Admin URL configuration</title>
<para>

View file

@ -30,7 +30,6 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.common.util.HostUtils;
import org.keycloak.util.JsonSerialization;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.UriUtils;
@ -91,7 +90,7 @@ public class AdminClient {
try {
HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(getBaseUrl(request) + "/auth")
HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(getRequestOrigin(request) + "/auth")
.path(ServiceUrlConstants.TOKEN_PATH).build("demo"));
List <NameValuePair> formparams = new ArrayList <NameValuePair>();
formparams.add(new BasicNameValuePair("username", "admin"));
@ -124,7 +123,7 @@ public class AdminClient {
try {
HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(getBaseUrl(request) + "/auth")
HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(UriUtils.getOrigin(request.getRequestURL().toString()) + "/auth")
.path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
.build("demo"));
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
@ -152,7 +151,7 @@ public class AdminClient {
HttpClient client = new DefaultHttpClient();
try {
HttpGet get = new HttpGet(getBaseUrl(request) + "/auth/admin/realms/demo/roles");
HttpGet get = new HttpGet(UriUtils.getOrigin(request.getRequestURL().toString()) + "/auth/admin/realms/demo/roles");
get.addHeader("Authorization", "Bearer " + res.getToken());
try {
HttpResponse response = client.execute(get);
@ -174,13 +173,8 @@ public class AdminClient {
}
}
public static String getBaseUrl(HttpServletRequest request) {
String useHostname = request.getServletContext().getInitParameter("useHostname");
if (useHostname != null && "true".equalsIgnoreCase(useHostname)) {
return "http://" + HostUtils.getHostName() + ":8080";
} else {
return UriUtils.getOrigin(request.getRequestURL().toString());
}
public static String getRequestOrigin(HttpServletRequest request) {
return UriUtils.getOrigin(request.getRequestURL().toString());
}
}

View file

@ -23,9 +23,4 @@
<module-name>admin-access</module-name>
<context-param>
<param-name>useHostname</param-name>
<param-value>false</param-value>
</context-param>
</web-app>

View file

@ -23,7 +23,7 @@ import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.common.util.UriUtils;
import org.keycloak.representations.IDToken;
import org.keycloak.util.JsonSerialization;
@ -66,7 +66,7 @@ public class CustomerDatabaseClient {
HttpClient client = new DefaultHttpClient();
try {
HttpGet get = new HttpGet(AdapterUtils.getOriginForRestCalls(req.getRequestURL().toString(), session) + "/database/customers");
HttpGet get = new HttpGet(UriUtils.getOrigin(req.getRequestURL().toString()) + "/database/customers");
get.addHeader("Authorization", "Bearer " + session.getTokenString());
try {
HttpResponse response = client.execute(get);

View file

@ -24,6 +24,7 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.common.util.UriUtils;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.util.JsonSerialization;
@ -59,7 +60,7 @@ public class AdminClient {
HttpClient client = new DefaultHttpClient();
try {
HttpGet get = new HttpGet(AdapterUtils.getOriginForRestCalls(req.getRequestURL().toString(), session) + "/auth/admin/realms/demo/roles");
HttpGet get = new HttpGet(UriUtils.getOrigin(req.getRequestURL().toString()) + "/auth/admin/realms/demo/roles");
get.addHeader("Authorization", "Bearer " + session.getTokenString());
try {
HttpResponse response = client.execute(get);

View file

@ -23,7 +23,7 @@ import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.common.util.UriUtils;
import org.keycloak.representations.IDToken;
import org.keycloak.util.JsonSerialization;
@ -66,7 +66,7 @@ public class CustomerDatabaseClient {
HttpClient client = new DefaultHttpClient();
try {
HttpGet get = new HttpGet(AdapterUtils.getOriginForRestCalls(req.getRequestURL().toString(), session) + "/database/customers");
HttpGet get = new HttpGet(UriUtils.getOrigin(req.getRequestURL().toString()) + "/database/customers");
get.addHeader("Authorization", "Bearer " + session.getTokenString());
try {
HttpResponse response = client.execute(get);

View file

@ -23,7 +23,7 @@ import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.common.util.UriUtils;
import org.keycloak.util.JsonSerialization;
import javax.servlet.http.HttpServletRequest;
@ -57,7 +57,7 @@ public class ProductDatabaseClient
HttpClient client = new DefaultHttpClient();
try {
HttpGet get = new HttpGet(AdapterUtils.getOriginForRestCalls(req.getRequestURL().toString(), session) + "/database/products");
HttpGet get = new HttpGet(UriUtils.getOrigin(req.getRequestURL().toString()) + "/database/products");
get.addHeader("Authorization", "Bearer " + session.getTokenString());
try {
HttpResponse response = client.execute(get);

View file

@ -24,7 +24,7 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.common.util.UriUtils;
import org.keycloak.servlet.ServletOAuthClient;
import org.keycloak.util.JsonSerialization;
@ -123,7 +123,7 @@ public class DatabaseClient {
public String getBaseUrl() {
KeycloakSecurityContext session = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
return AdapterUtils.getOriginForRestCalls(request.getRequestURL().toString(), session);
return UriUtils.getOrigin(request.getRequestURL().toString());
}
}

View file

@ -23,7 +23,6 @@ import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.ServerRequest;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.servlet.ServletOAuthClient;
@ -100,7 +99,7 @@ public class ProductDatabaseClient {
ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(AdapterUtils.getOriginForRestCalls(request.getRequestURL().toString(), session) + "/database/products");
HttpGet get = new HttpGet(UriUtils.getOrigin(request.getRequestURL().toString()) + "/database/products");
get.addHeader("Authorization", "Bearer " + accessToken);
try {
HttpResponse response = client.execute(get);
@ -119,19 +118,4 @@ public class ProductDatabaseClient {
}
}
public static String getBaseUrl(ServletOAuthClient oAuthClient, HttpServletRequest request) {
switch (oAuthClient.getRelativeUrlsUsed()) {
case ALL_REQUESTS:
// Resolve baseURI from the request
return UriUtils.getOrigin(request.getRequestURL().toString());
case BROWSER_ONLY:
// Resolve baseURI from the codeURL (This is already non-relative and based on our hostname)
return UriUtils.getOrigin(oAuthClient.getTokenUrl());
case NEVER:
return "";
default:
return "";
}
}
}