Merge pull request #3579 from mposolda/master

KEYCLOAK-3824 Ensure sending notBefore invalidates JWKPublicKeyLocator
This commit is contained in:
Marek Posolda 2016-12-01 17:45:55 +01:00 committed by GitHub
commit bed592460d
9 changed files with 58 additions and 29 deletions

View file

@ -342,6 +342,12 @@ public class AdapterDeploymentContext {
return delegate.getNotBefore(); return delegate.getNotBefore();
} }
@Override
public void updateNotBefore(int notBefore) {
delegate.setNotBefore(notBefore);
getPublicKeyLocator().reset(this);
}
@Override @Override
public void setExposeToken(boolean exposeToken) { public void setExposeToken(boolean exposeToken) {
delegate.setExposeToken(exposeToken); delegate.setExposeToken(exposeToken);

View file

@ -329,6 +329,11 @@ public class KeycloakDeployment {
this.notBefore = notBefore; this.notBefore = notBefore;
} }
public void updateNotBefore(int notBefore) {
this.notBefore = notBefore;
getPublicKeyLocator().reset(this);
}
public boolean isAlwaysRefreshToken() { public boolean isAlwaysRefreshToken() {
return alwaysRefreshToken; return alwaysRefreshToken;
} }

View file

@ -357,7 +357,7 @@ public class OAuthRequestAuthenticator {
return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null); return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null);
} }
if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) { if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) {
deployment.setNotBefore(tokenResponse.getNotBeforePolicy()); deployment.updateNotBefore(tokenResponse.getNotBeforePolicy());
} }
if (token.getIssuedAt() < deployment.getNotBefore()) { if (token.getIssuedAt() < deployment.getNotBefore()) {
log.error("Stale token"); log.error("Stale token");

View file

@ -155,7 +155,7 @@ public class PreAuthActionsHandler {
} else { } else {
log.debugf("logout of all sessions for application '%s'", action.getResource()); log.debugf("logout of all sessions for application '%s'", action.getResource());
if (action.getNotBefore() > deployment.getNotBefore()) { if (action.getNotBefore() > deployment.getNotBefore()) {
deployment.setNotBefore(action.getNotBefore()); deployment.updateNotBefore(action.getNotBefore());
} }
userSessionManagement.logoutAll(); userSessionManagement.logoutAll();
} }
@ -177,7 +177,7 @@ public class PreAuthActionsHandler {
} }
PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class); PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
if (!validateAction(action)) return; if (!validateAction(action)) return;
deployment.setNotBefore(action.getNotBefore()); deployment.updateNotBefore(action.getNotBefore());
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View file

@ -144,7 +144,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
} }
if (response.getNotBeforePolicy() > deployment.getNotBefore()) { if (response.getNotBeforePolicy() > deployment.getNotBefore()) {
deployment.setNotBefore(response.getNotBeforePolicy()); deployment.updateNotBefore(response.getNotBeforePolicy());
} }
this.token = token; this.token = token;

View file

@ -37,4 +37,9 @@ public class HardcodedPublicKeyLocator implements PublicKeyLocator {
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) { public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
return publicKey; return publicKey;
} }
@Override
public void reset(KeycloakDeployment deployment) {
}
} }

View file

@ -57,20 +57,24 @@ public class JWKPublicKeyLocator implements PublicKeyLocator {
} }
// Check if we are allowed to send request // Check if we are allowed to send request
if (currentTime > lastRequestTime + minTimeBetweenRequests) { synchronized (this) {
synchronized (this) { currentTime = Time.currentTime();
currentTime = Time.currentTime(); if (currentTime > lastRequestTime + minTimeBetweenRequests) {
if (currentTime > lastRequestTime + minTimeBetweenRequests) { sendRequest(deployment);
sendRequest(deployment); lastRequestTime = currentTime;
lastRequestTime = currentTime; } else {
} else { log.debugf("Won't send request to realm jwks url. Last request time was %d", lastRequestTime);
log.debugf("Won't send request to realm jwks url. Last request time was %d", lastRequestTime);
}
} }
return lookupCachedKey(publicKeyCacheTtl, currentTime, kid);
} }
}
return lookupCachedKey(publicKeyCacheTtl, currentTime, kid);
@Override
public void reset(KeycloakDeployment deployment) {
sendRequest(deployment);
lastRequestTime = Time.currentTime();
} }

View file

@ -34,4 +34,11 @@ public interface PublicKeyLocator {
*/ */
PublicKey getPublicKey(String kid, KeycloakDeployment deployment); PublicKey getPublicKey(String kid, KeycloakDeployment deployment);
/**
* Reset the state of locator (eg. clear the cached keys)
*
* @param deployment
*/
void reset(KeycloakDeployment deployment);
} }

View file

@ -187,8 +187,6 @@ public class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractServletsAd
// KEYCLOAK-3824: Test for public-key-cache-ttl // KEYCLOAK-3824: Test for public-key-cache-ttl
@Test @Test
public void testPublicKeyCacheTtl() { public void testPublicKeyCacheTtl() {
driver.manage().timeouts().pageLoadTimeout(1000, TimeUnit.SECONDS);
// increase accessTokenLifespan to 1200 // increase accessTokenLifespan to 1200
RealmRepresentation demoRealm = adminClient.realm(DEMO).toRepresentation(); RealmRepresentation demoRealm = adminClient.realm(DEMO).toRepresentation();
demoRealm.setAccessTokenLifespan(1200); demoRealm.setAccessTokenLifespan(1200);
@ -202,8 +200,10 @@ public class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractServletsAd
int status = invokeRESTEndpoint(accessTokenString); int status = invokeRESTEndpoint(accessTokenString);
Assert.assertEquals(200, status); Assert.assertEquals(200, status);
// Invalidate realm public key // Re-generate realm public key and remove the old key
String oldKeyId = getActiveKeyId();
generateNewRealmKey(); generateNewRealmKey();
adminClient.realm(DEMO).components().component(oldKeyId).remove();
// Send REST request to the customer-db app. Should be still succcessfully authenticated as the JWKPublicKeyLocator cache is still valid // Send REST request to the customer-db app. Should be still succcessfully authenticated as the JWKPublicKeyLocator cache is still valid
status = invokeRESTEndpoint(accessTokenString); status = invokeRESTEndpoint(accessTokenString);
@ -225,6 +225,8 @@ public class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractServletsAd
// KEYCLOAK-3823: Test that sending notBefore policy invalidates JWKPublicKeyLocator cache // KEYCLOAK-3823: Test that sending notBefore policy invalidates JWKPublicKeyLocator cache
@Test @Test
public void testPublicKeyCacheInvalidatedWhenPushedNotBefore() { public void testPublicKeyCacheInvalidatedWhenPushedNotBefore() {
driver.manage().timeouts().pageLoadTimeout(1000, TimeUnit.SECONDS);
// increase accessTokenLifespan to 1200 // increase accessTokenLifespan to 1200
RealmRepresentation demoRealm = adminClient.realm(DEMO).toRepresentation(); RealmRepresentation demoRealm = adminClient.realm(DEMO).toRepresentation();
demoRealm.setAccessTokenLifespan(1200); demoRealm.setAccessTokenLifespan(1200);
@ -234,20 +236,20 @@ public class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractServletsAd
loginToTokenMinTtlApp(); loginToTokenMinTtlApp();
String accessTokenString = tokenMinTTLPage.getAccessTokenString(); String accessTokenString = tokenMinTTLPage.getAccessTokenString();
// Send REST request to customer-db app. I should be successfully authenticated // Generate new realm public key
String oldKeyId = getActiveKeyId();
generateNewRealmKey();
// Send REST request to customer-db app. It should be successfully authenticated even that token is signed by the old key
int status = invokeRESTEndpoint(accessTokenString); int status = invokeRESTEndpoint(accessTokenString);
Assert.assertEquals(200, status); Assert.assertEquals(200, status);
// Invalidate realm public key // Remove the old realm key now
generateNewRealmKey(); adminClient.realm(DEMO).components().component(oldKeyId).remove();
// Set some offset to ensure pushing notBefore will pass // Set some offset to ensure pushing notBefore will pass
setAdapterAndServerTimeOffset(130, customerDb.toString() + "/unsecured/foo", tokenMinTTLPage.toString() + "/unsecured/foo"); setAdapterAndServerTimeOffset(130, customerDb.toString() + "/unsecured/foo", tokenMinTTLPage.toString() + "/unsecured/foo");
// Send REST request to the REST app. Should be still succcessfully authenticated as the JWKPublicKeyLocator cache is still valid
status = invokeRESTEndpoint(accessTokenString);
Assert.assertEquals(200, status);
// Send notBefore policy from the realm // Send notBefore policy from the realm
demoRealm.setNotBefore(Time.currentTime() - 1); demoRealm.setNotBefore(Time.currentTime() - 1);
adminClient.realm(DEMO).update(demoRealm); adminClient.realm(DEMO).update(demoRealm);
@ -281,9 +283,6 @@ public class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractServletsAd
private void generateNewRealmKey() { private void generateNewRealmKey() {
String realmId = adminClient.realm(DEMO).toRepresentation().getId(); String realmId = adminClient.realm(DEMO).toRepresentation().getId();
String oldKeyId = adminClient.realm(DEMO).components().query(realmId, KeyProvider.class.getName())
.get(0).getId();
ComponentRepresentation keys = new ComponentRepresentation(); ComponentRepresentation keys = new ComponentRepresentation();
keys.setName("generated"); keys.setName("generated");
keys.setProviderType(KeyProvider.class.getName()); keys.setProviderType(KeyProvider.class.getName());
@ -294,9 +293,12 @@ public class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractServletsAd
Response response = adminClient.realm(DEMO).components().add(keys); Response response = adminClient.realm(DEMO).components().add(keys);
assertEquals(201, response.getStatus()); assertEquals(201, response.getStatus());
response.close(); response.close();
}
// Remove original key private String getActiveKeyId() {
adminClient.realm(DEMO).components().component(oldKeyId).remove(); String realmId = adminClient.realm(DEMO).toRepresentation().getId();
return adminClient.realm(DEMO).components().query(realmId, KeyProvider.class.getName())
.get(0).getId();
} }