Skip to content

Commit

Permalink
KEYCLOAK-2028: Add preemptive access token refresh support
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bloy-smartling authored and mposolda committed Jun 9, 2016
1 parent f6d075f commit ec180db
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,16 @@ public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLog
}
}

@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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public class KeycloakDeployment {
protected boolean turnOffChangeSessionIdOnLogin;

protected volatile int notBefore;
protected int tokenMinimumTimeToLive;

public KeycloakDeployment() {
}
Expand Down Expand Up @@ -357,4 +358,12 @@ public boolean isTurnOffChangeSessionIdOnLogin() {
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
this.turnOffChangeSessionIdOnLogin = turnOffChangeSessionIdOnLogin;
}

public int getTokenMinimumTimeToLive() {
return tokenMinimumTimeToLive;
}

public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ protected KeycloakDeployment internalBuild(AdapterConfig adapterConfig) {
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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
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;
Expand Down Expand Up @@ -77,6 +78,10 @@ public boolean isActive() {
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;
}
Expand All @@ -95,7 +100,7 @@ public boolean refreshExpiredToken(boolean checkActive) {
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?
Expand Down Expand Up @@ -130,6 +135,13 @@ public boolean refreshExpiredToken(boolean checkActive) {
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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public void load() throws Exception {
assertEquals(1000, deployment.getRegisterNodePeriod());
assertEquals(TokenStore.COOKIE, deployment.getTokenStore());
assertEquals("email", deployment.getPrincipalAttribute());
assertEquals(10, deployment.getTokenMinimumTimeToLive());
}

@Test
Expand Down
3 changes: 2 additions & 1 deletion adapters/oidc/adapter-core/src/test/resources/keycloak.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"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 {

Expand Down Expand Up @@ -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}.
Expand Down Expand Up @@ -194,4 +196,13 @@ public String getProxyUrl() {
public void setProxyUrl(String proxyUrl) {
this.proxyUrl = proxyUrl;
}

public int getTokenMinimumTimeToLive() {
return tokenMinimumTimeToLive;
}

public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
"token-store": "cookie",
"credentials": {
"secret": "password"
}
},
"token-minimum-time-to-live": 1
}

0 comments on commit ec180db

Please sign in to comment.