diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java index fd54ce2b3e..7f62fd18bb 100755 --- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java +++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java @@ -17,7 +17,7 @@ import org.codehaus.jackson.annotate.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" + "auth-server-url-for-backend-requests", "always-refresh-token" }) public class AdapterConfig extends BaseAdapterConfig { @@ -39,6 +39,8 @@ public class AdapterConfig extends BaseAdapterConfig { protected int connectionPoolSize = 20; @JsonProperty("auth-server-url-for-backend-requests") protected String authServerUrlForBackendRequests; + @JsonProperty("always-refresh-token") + protected boolean alwaysRefreshToken = false; public boolean isAllowAnyHostname() { return allowAnyHostname; @@ -111,4 +113,12 @@ public class AdapterConfig extends BaseAdapterConfig { public void setAuthServerUrlForBackendRequests(String authServerUrlForBackendRequests) { this.authServerUrlForBackendRequests = authServerUrlForBackendRequests; } + + public boolean isAlwaysRefreshToken() { + return alwaysRefreshToken; + } + + public void setAlwaysRefreshToken(boolean alwaysRefreshToken) { + this.alwaysRefreshToken = alwaysRefreshToken; + } } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java index f1e52923ad..69e61aaf20 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java @@ -326,6 +326,16 @@ public class AdapterDeploymentContext { public void setCorsAllowedHeaders(String corsAllowedHeaders) { delegate.setCorsAllowedHeaders(corsAllowedHeaders); } + + @Override + public boolean isAlwaysRefreshToken() { + return delegate.isAlwaysRefreshToken(); + } + + @Override + public void setAlwaysRefreshToken(boolean alwaysRefreshToken) { + delegate.setAlwaysRefreshToken(alwaysRefreshToken); + } } protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) { diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java index db284b19e0..c3761126f8 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java @@ -47,6 +47,7 @@ public class KeycloakDeployment { protected String corsAllowedHeaders; protected String corsAllowedMethods; protected boolean exposeToken; + protected boolean alwaysRefreshToken; protected volatile int notBefore; public KeycloakDeployment() { @@ -281,4 +282,11 @@ public class KeycloakDeployment { this.notBefore = notBefore; } + public boolean isAlwaysRefreshToken() { + return alwaysRefreshToken; + } + + public void setAlwaysRefreshToken(boolean alwaysRefreshToken) { + this.alwaysRefreshToken = alwaysRefreshToken; + } } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java index 5aa996b428..6e5c29fd86 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java @@ -35,7 +35,7 @@ public class KeycloakDeploymentBuilder { String realmKeyPem = adapterConfig.getRealmKey(); if (realmKeyPem != null) { - PublicKey realmKey = null; + PublicKey realmKey; try { realmKey = PemUtils.decodePublicKey(realmKeyPem); } catch (Exception e) { @@ -60,9 +60,7 @@ public class KeycloakDeploymentBuilder { } deployment.setBearerOnly(adapterConfig.isBearerOnly()); - - if (adapterConfig.isBearerOnly()) { - } + deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken()); if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) { throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url"); @@ -82,7 +80,7 @@ public class KeycloakDeploymentBuilder { public static KeycloakDeployment build(InputStream is) { ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory()); mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT); - AdapterConfig adapterConfig = null; + AdapterConfig adapterConfig; try { adapterConfig = mapper.readValue(is, AdapterConfig.class); } catch (IOException e) { diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java index 7dfe62c722..3fc5b3d6e2 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java @@ -32,13 +32,13 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext @Override public AccessToken getToken() { - refreshExpiredToken(); + refreshExpiredToken(true); return super.getToken(); } @Override public String getTokenString() { - refreshExpiredToken(); + refreshExpiredToken(true); return super.getTokenString(); } @@ -62,12 +62,19 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext this.deployment = deployment; } - public void refreshExpiredToken() { - if (log.isTraceEnabled()) { - log.trace("checking whether to refresh."); + /** + * @param checkActive if true, then we won't send refresh request if current accessToken is still active. + * @return true if accessToken is active or was successfully refreshed + */ + public boolean refreshExpiredToken(boolean checkActive) { + if (checkActive) { + if (log.isTraceEnabled()) { + log.trace("checking whether to refresh."); + } + if (isActive()) return true; } - if (isActive()) return; - if (this.deployment == null || refreshToken == null) return; // Might be serialized in HttpSession? + + if (this.deployment == null || refreshToken == null) return false; // Might be serialized in HttpSession? if (log.isTraceEnabled()) { log.trace("Doing refresh"); @@ -77,10 +84,10 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext response = ServerRequest.invokeRefresh(deployment, refreshToken); } catch (IOException e) { log.error("Refresh token failure", e); - return; + return false; } catch (ServerRequest.HttpFailure httpFailure) { log.error("Refresh token failure status: " + httpFailure.getStatus() + " " + httpFailure.getError()); - return; + return false; } if (log.isTraceEnabled()) { log.trace("received refresh response"); @@ -100,7 +107,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext this.token = token; this.refreshToken = response.getRefreshToken(); this.tokenString = tokenString; - + return true; } diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java index eb3975630f..7392270e7f 100755 --- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java +++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java @@ -175,12 +175,12 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif if (session == null) return; // just in case session got serialized if (session.getDeployment() == null) session.setDeployment(deploymentContext.resolveDeployment(facade)); - if (session.isActive()) return; + if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return; // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will // not be updated - session.refreshExpiredToken(); - if (session.isActive()) return; + boolean success = session.refreshExpiredToken(false); + if (success && session.isActive()) return; request.getSessionInternal().removeNote(KeycloakSecurityContext.class.getName()); request.setUserPrincipal(null); diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java index 2fd8be480a..2905c8f68f 100755 --- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java +++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java @@ -177,12 +177,12 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif if (session == null) return; // just in case session got serialized if (session.getDeployment() == null) session.setDeployment(deploymentContext.resolveDeployment(facade)); - if (session.isActive()) return; + if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return; // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will // not be updated - session.refreshExpiredToken(); - if (session.isActive()) return; + boolean success = session.refreshExpiredToken(false); + if (success && session.isActive()) return; request.getSessionInternal().removeNote(KeycloakSecurityContext.class.getName()); request.setUserPrincipal(null); diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java index 58cbb023d5..472bc90410 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java @@ -92,14 +92,14 @@ public class KeycloakUndertowAccount implements Account, Serializable, KeycloakA public boolean isActive() { // this object may have been serialized, so we need to reset realm config/metadata RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext(); - if (session.isActive()) { + if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) { log.debug("session is active"); return true; } - log.debug("session is not active try refresh"); - session.refreshExpiredToken(); - if (!session.isActive()) { + log.debug("session is not active or refresh is enforced. Try refresh"); + boolean success = session.refreshExpiredToken(false); + if (!success || !session.isActive()) { log.debug("session is not active return with failure"); return false; diff --git a/testsuite/docker-cluster/shared-files/deploy-examples.sh b/testsuite/docker-cluster/shared-files/deploy-examples.sh index 10f9643798..efc54caad4 100644 --- a/testsuite/docker-cluster/shared-files/deploy-examples.sh +++ b/testsuite/docker-cluster/shared-files/deploy-examples.sh @@ -23,6 +23,10 @@ done; # Configure admin-access.war sed -i -e 's/false/true/' admin-access.war/WEB-INF/web.xml +# Enforce refreshing token for product-portal and customer-portal war +sed -i -e 's/\"\/auth\",/&\n \"always-refresh-token\": true,/' customer-portal.war/WEB-INF/keycloak.json; +sed -i -e 's/\"\/auth\",/&\n \"always-refresh-token\": true,/' product-portal.war/WEB-INF/keycloak.json; + # Configure other examples for I in *.war/WEB-INF/keycloak.json; do sed -i -e 's/\"\/auth\",/&\n \"auth-server-url-for-backend-requests\": \"http:\/\/\$\{jboss.host.name\}:8080\/auth\",/' $I;