diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java index 7ff049a5fa..89a7bfe520 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java @@ -458,6 +458,16 @@ public class AdapterDeploymentContext { } } + @Override + public int getTokenMinimumTimeToLive() { + return delegate.getTokenMinimumTimeToLive(); + } + + @Override + public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) { + delegate.setTokenMinimumTimeToLive(tokenMinimumTimeToLive); + } + protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) { KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(base); URI request = URI.create(facade.getRequest().getURI()); 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 cca0adf4fe..f95e3f81ca 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 @@ -77,6 +77,7 @@ public class KeycloakDeployment { protected boolean turnOffChangeSessionIdOnLogin; protected volatile int notBefore; + protected int tokenMinimumTimeToLive; public KeycloakDeployment() { } @@ -357,4 +358,12 @@ public class KeycloakDeployment { public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) { this.turnOffChangeSessionIdOnLogin = turnOffChangeSessionIdOnLogin; } + + public int getTokenMinimumTimeToLive() { + return tokenMinimumTimeToLive; + } + + public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) { + this.tokenMinimumTimeToLive = tokenMinimumTimeToLive; + } } 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 ab77491157..89f2a75809 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 @@ -94,6 +94,7 @@ public class KeycloakDeploymentBuilder { deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken()); deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup()); deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod()); + deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive()); 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"); diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java index bdb57766df..35a38eeefb 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java @@ -21,6 +21,7 @@ import org.jboss.logging.Logger; import org.keycloak.KeycloakSecurityContext; import org.keycloak.RSATokenVerifier; import org.keycloak.common.VerificationException; +import org.keycloak.common.util.Time; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.IDToken; @@ -77,6 +78,10 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext return token != null && this.token.isActive() && this.token.getIssuedAt() > deployment.getNotBefore(); } + public boolean isTokenTimeToLiveSufficient(AccessToken token) { + return token != null && (token.getExpiration() - this.deployment.getTokenMinimumTimeToLive()) > Time.currentTime(); + } + public KeycloakDeployment getDeployment() { return deployment; } @@ -95,7 +100,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext if (log.isTraceEnabled()) { log.trace("checking whether to refresh."); } - if (isActive()) return true; + if (isActive() && isTokenTimeToLiveSufficient(this.token)) return true; } if (this.deployment == null || refreshToken == null) return false; // Might be serialized in HttpSession? @@ -130,6 +135,13 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext log.error("failed verification of token"); return false; } + + // If the TTL is greater-or-equal to the expire time on the refreshed token, have to abort or go into an infinite refresh loop + if (!isTokenTimeToLiveSufficient(token)) { + log.error("failed to refresh the token with a longer time-to-live than the minimum"); + return false; + } + if (response.getNotBeforePolicy() > deployment.getNotBefore()) { deployment.setNotBefore(response.getNotBeforePolicy()); } 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 93d1ea7896..4be511023c 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 @@ -61,6 +61,7 @@ public class KeycloakDeploymentBuilderTest { assertEquals(1000, deployment.getRegisterNodePeriod()); assertEquals(TokenStore.COOKIE, deployment.getTokenStore()); assertEquals("email", deployment.getPrincipalAttribute()); + assertEquals(10, deployment.getTokenMinimumTimeToLive()); } @Test diff --git a/adapters/oidc/adapter-core/src/test/resources/keycloak.json b/adapters/oidc/adapter-core/src/test/resources/keycloak.json index 5a41841efa..7bf269f2c1 100644 --- a/adapters/oidc/adapter-core/src/test/resources/keycloak.json +++ b/adapters/oidc/adapter-core/src/test/resources/keycloak.json @@ -28,5 +28,6 @@ "register-node-at-startup": true, "register-node-period": 1000, "token-store": "cookie", - "principal-attribute": "email" + "principal-attribute": "email", + "token-minimum-time-to-live": 10 } \ No newline at end of file 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 87b3ab2ad9..d226099733 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 @@ -36,7 +36,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; "client-keystore", "client-keystore-password", "client-key-password", "always-refresh-token", "register-node-at-startup", "register-node-period", "token-store", "principal-attribute", - "proxy-url" + "proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live" }) public class AdapterConfig extends BaseAdapterConfig { @@ -68,6 +68,8 @@ public class AdapterConfig extends BaseAdapterConfig { protected String principalAttribute; @JsonProperty("turn-off-change-session-id-on-login") protected Boolean turnOffChangeSessionIdOnLogin; + @JsonProperty("token-minimum-time-to-live") + protected int tokenMinimumTimeToLive = 0; /** * The Proxy url to use for requests to the auth-server, configurable via the adapter config property {@code proxy-url}. @@ -194,4 +196,13 @@ public class AdapterConfig extends BaseAdapterConfig { public void setProxyUrl(String proxyUrl) { this.proxyUrl = proxyUrl; } + + public int getTokenMinimumTimeToLive() { + return tokenMinimumTimeToLive; + } + + public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) { + this.tokenMinimumTimeToLive = tokenMinimumTimeToLive; + } + } diff --git a/testsuite/integration/src/test/resources/adapter-test/cust-app-cookie-keycloak.json b/testsuite/integration/src/test/resources/adapter-test/cust-app-cookie-keycloak.json index 92fe860ae7..200cdd3daa 100644 --- a/testsuite/integration/src/test/resources/adapter-test/cust-app-cookie-keycloak.json +++ b/testsuite/integration/src/test/resources/adapter-test/cust-app-cookie-keycloak.json @@ -8,5 +8,6 @@ "token-store": "cookie", "credentials": { "secret": "password" - } + }, + "token-minimum-time-to-live": 1 } \ No newline at end of file