KEYCLOAK-2028: Add preemptive access token refresh support
Add a new keycloak.json property and mechanism to automatically refresh access tokens if they are going to expire in less than a configurable amount of time.
This commit is contained in:
parent
f6d075fbe4
commit
ec180db39f
8 changed files with 50 additions and 4 deletions
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,5 +8,6 @@
|
|||
"token-store": "cookie",
|
||||
"credentials": {
|
||||
"secret": "password"
|
||||
}
|
||||
},
|
||||
"token-minimum-time-to-live": 1
|
||||
}
|
Loading…
Reference in a new issue