KEYCLOAK-4538 Configurable clock skew when validating tokens (#5014)

* [master]: fix type for checkLoginIframeInterval

* [master]: KEYCLOAK-4538 Feature to tolerate a configurable amount of seconds of clock skew when validating tokens

* [master]: KEYCLOAK-4538 Fix unit test scenarios for token clock skew

* [master]: KEYCLOAK-4538 Reverted wildcard imports

* [master]: fix unit test to use longer intervals to make test less fragile.
This commit is contained in:
Oskars 2018-04-16 12:09:25 +03:00 committed by Marek Posolda
parent e8a07c9a6c
commit 3bef6d5066
6 changed files with 70 additions and 10 deletions

View file

@ -91,10 +91,9 @@ public class JsonWebToken implements Serializable {
return this;
}
@JsonIgnore
public boolean isNotBefore() {
return Time.currentTime() >= notBefore;
public boolean isNotBefore(int allowedTimeSkew) {
return Time.currentTime() + allowedTimeSkew >= notBefore;
}
/**
@ -104,7 +103,12 @@ public class JsonWebToken implements Serializable {
*/
@JsonIgnore
public boolean isActive() {
return (!isExpired() || expiration == 0) && (isNotBefore() || notBefore == 0);
return isActive(0);
}
@JsonIgnore
public boolean isActive(int allowedTimeSkew) {
return (!isExpired() || expiration == 0) && (isNotBefore(allowedTimeSkew) || notBefore == 0);
}
public int getIssuedAt() {

View file

@ -18,11 +18,13 @@
package org.keycloak.jose;
import org.junit.Test;
import org.keycloak.common.util.Time;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
@ -59,4 +61,40 @@ public class JsonWebTokenTest {
assertTrue(JsonSerialization.writeValueAsPrettyString(jsonWebToken).contains("\"aud\" : [ \"test\", \"test2\" ]"));
}
@Test
public void isActiveReturnFalseWhenBeforeTimeInFuture() {
int currentTime = Time.currentTime();
int futureTime = currentTime + 10;
JsonWebToken jsonWebToken = new JsonWebToken();
jsonWebToken.notBefore(futureTime);
assertFalse(jsonWebToken.isActive());
}
@Test
public void isActiveReturnTrueWhenBeforeTimeInPast() {
int currentTime = Time.currentTime();
int pastTime = currentTime - 10;
JsonWebToken jsonWebToken = new JsonWebToken();
jsonWebToken.notBefore(pastTime);
assertTrue(jsonWebToken.isActive());
}
@Test
public void isActiveShouldReturnTrueWhenBeforeTimeInFutureWithinTimeSkew() {
int notBeforeTime = Time.currentTime() + 5;
int allowedClockSkew = 10;
JsonWebToken jsonWebToken = new JsonWebToken();
jsonWebToken.notBefore(notBeforeTime);
assertTrue(jsonWebToken.isActive(allowedClockSkew));
}
@Test
public void isActiveShouldReturnFalseWhenWhenBeforeTimeInFutureOutsideTimeSkew() {
int notBeforeTime = Time.currentTime() + 10;
int allowedClockSkew = 5;
JsonWebToken jsonWebToken = new JsonWebToken();
jsonWebToken.notBefore(notBeforeTime);
assertFalse(jsonWebToken.isActive(allowedClockSkew));
}
}

View file

@ -21,7 +21,6 @@ import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.ExchangeExternalToken;
import org.keycloak.broker.provider.IdentityBrokerException;
@ -478,7 +477,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
String iss = token.getIssuer();
if (!token.isActive()) {
if (!token.isActive(getConfig().getAllowedClockSkew())) {
throw new IdentityBrokerException("Token is no longer valid");
}

View file

@ -110,7 +110,16 @@ public class OIDCIdentityProviderConfig extends OAuth2IdentityProviderConfig {
getConfig().put("disableUserInfo", String.valueOf(disable));
}
public int getAllowedClockSkew() {
String allowedClockSkew = getConfig().get("allowedClockSkew");
if (allowedClockSkew == null || allowedClockSkew.isEmpty()) {
return 0;
}
try {
return Integer.parseInt(getConfig().get("allowedClockSkew"));
} catch (NumberFormatException e) {
// ignore it and use default
return 0;
}
}
}

View file

@ -561,6 +561,8 @@ validating-public-key=Validating Public Key
identity-provider.validating-public-key.tooltip=The public key in PEM format that must be used to verify external IDP signatures.
validating-public-key-id=Validating Public Key Id
identity-provider.validating-public-key-id.tooltip=Explicit ID of the validating public key given above if the key ID. Leave blank if the key above should be used always, regardless of key ID specified by external IDP; set it if the key should only be used for verifying if key ID from external IDP matches.
identity-provider.allowed-clock-skew=Allowed clock skew
identity-provider.allowed-clock-skew.tooltip=Clock skew in seconds that is tolerated when validating identity provider tokens. Default value is zero.
import-external-idp-config=Import External IDP Config
import-external-idp-config.tooltip=Allows you to load external IDP metadata from a config file or to download it from a URL.
import-from-url=Import from URL

View file

@ -246,6 +246,14 @@
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="allowedClockSkew">{{:: 'allowed-clock-skew' | translate}}</label>
<div class="col-md-6">
<input ng-model="identityProvider.config.allowedClockSkew" id="allowedClockSkew" type="text" class="form-control"/>
</div>
<kc-tooltip>{{:: 'identity-provider.allowed-clock-skew.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<fieldset data-ng-show="newIdentityProvider">
<legend uncollapsed><span class="text">{{:: 'import-external-idp-config' | translate}}</span> <kc-tooltip>{{:: 'import-external-idp-config.tooltip' | translate}}</kc-tooltip></legend>