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:
parent
e8a07c9a6c
commit
3bef6d5066
6 changed files with 70 additions and 10 deletions
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -35,7 +37,7 @@ public class JsonWebTokenTest {
|
|||
public void testAudSingle() throws IOException {
|
||||
String single = "{ \"aud\": \"test\" }";
|
||||
JsonWebToken s = JsonSerialization.readValue(single, JsonWebToken.class);
|
||||
assertArrayEquals(new String[] { "test" }, s.getAudience());
|
||||
assertArrayEquals(new String[]{"test"}, s.getAudience());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue