Clock time source for parsing

Clock source
This commit is contained in:
Les Hazlewood 2016-04-01 18:23:47 -07:00
commit 9e1ee67582
7 changed files with 164 additions and 7 deletions

View File

@ -0,0 +1,22 @@
package io.jsonwebtoken;
import io.jsonwebtoken.impl.DefaultClock;
import java.util.Date;
/**
* A clock represents a time source that can be used when creating and verifying JWTs.
*
* @since 0.7.0
*/
public interface Clock {
public static final Clock DEFAULT = new DefaultClock();
/**
* Returns the clock's current timestamp at the instant the method is invoked.
*
* @return the clock's current timestamp at the instant the method is invoked.
*/
Date now();
}

View File

@ -15,6 +15,8 @@
*/ */
package io.jsonwebtoken; package io.jsonwebtoken;
import io.jsonwebtoken.impl.DefaultClock;
import java.security.Key; import java.security.Key;
import java.util.Date; import java.util.Date;
@ -124,6 +126,16 @@ public interface JwtParser {
*/ */
JwtParser require(String claimName, Object value); JwtParser require(String claimName, Object value);
/**
* Sets the {@link Clock} that determines the timestamp to use when validating the parsed JWT.
* The parser uses a {@link DefaultClock DefaultClock} instance by default.
*
* @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT.
* @return the builder instance for method chaining.
* @since 0.7.0
*/
JwtParser setClock(Clock clock);
/** /**
* Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not * Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not
* a JWS (no signature), this key is not used. * a JWS (no signature), this key is not used.

View File

@ -0,0 +1,23 @@
package io.jsonwebtoken.impl;
import io.jsonwebtoken.Clock;
import java.util.Date;
/**
* Default {@link Clock} implementation.
*
* @since 0.7.0
*/
public class DefaultClock implements Clock {
/**
* Simply returns <code>new {@link Date}()</code>.
*
* @return a new {@link Date} instance.
*/
@Override
public Date now() {
return new Date();
}
}

View File

@ -18,6 +18,7 @@ package io.jsonwebtoken.impl;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.ClaimJwtException; import io.jsonwebtoken.ClaimJwtException;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Clock;
import io.jsonwebtoken.CompressionCodec; import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.CompressionCodecResolver; import io.jsonwebtoken.CompressionCodecResolver;
import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.ExpiredJwtException;
@ -69,6 +70,8 @@ public class DefaultJwtParser implements JwtParser {
Claims expectedClaims = new DefaultClaims(); Claims expectedClaims = new DefaultClaims();
private Clock clock = Clock.DEFAULT;
@Override @Override
public JwtParser requireIssuedAt(Date issuedAt) { public JwtParser requireIssuedAt(Date issuedAt) {
expectedClaims.setIssuedAt(issuedAt); expectedClaims.setIssuedAt(issuedAt);
@ -127,6 +130,13 @@ public class DefaultJwtParser implements JwtParser {
return this; return this;
} }
@Override
public JwtParser setClock(Clock clock) {
Assert.notNull(clock, "Clock instance cannot be null.");
this.clock = clock;
return this;
}
@Override @Override
public JwtParser setSigningKey(byte[] key) { public JwtParser setSigningKey(byte[] key) {
Assert.notEmpty(key, "signing key cannot be null or empty."); Assert.notEmpty(key, "signing key cannot be null or empty.");
@ -346,16 +356,15 @@ public class DefaultJwtParser implements JwtParser {
//since 0.3: //since 0.3:
if (claims != null) { if (claims != null) {
Date now = null;
SimpleDateFormat sdf; SimpleDateFormat sdf;
final Date now = this.clock.now();
//https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4.1.4 //https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4.1.4
//token MUST NOT be accepted on or after any specified exp time: //token MUST NOT be accepted on or after any specified exp time:
Date exp = claims.getExpiration(); Date exp = claims.getExpiration();
if (exp != null) { if (exp != null) {
now = new Date();
if (now.equals(exp) || now.after(exp)) { if (now.equals(exp) || now.after(exp)) {
sdf = new SimpleDateFormat(ISO_8601_FORMAT); sdf = new SimpleDateFormat(ISO_8601_FORMAT);
String expVal = sdf.format(exp); String expVal = sdf.format(exp);
@ -371,10 +380,6 @@ public class DefaultJwtParser implements JwtParser {
Date nbf = claims.getNotBefore(); Date nbf = claims.getNotBefore();
if (nbf != null) { if (nbf != null) {
if (now == null) {
now = new Date();
}
if (now.before(nbf)) { if (now.before(nbf)) {
sdf = new SimpleDateFormat(ISO_8601_FORMAT); sdf = new SimpleDateFormat(ISO_8601_FORMAT);
String nbfVal = sdf.format(nbf); String nbfVal = sdf.format(nbf);

View File

@ -0,0 +1,39 @@
package io.jsonwebtoken.impl;
import io.jsonwebtoken.Clock;
import java.util.Date;
/**
* A {@code Clock} implementation that is constructed with a seed timestamp and always reports that same
* timestamp.
*
* @since 0.7.0
*/
public class FixedClock implements Clock {
private final Date now;
/**
* Creates a new fixed clock using <code>new {@link Date Date}()</code> as the seed timestamp. All calls to
* {@link #now now()} will always return this seed Date.
*/
public FixedClock() {
this(new Date());
}
/**
* Creates a new fixed clock using the specified seed timestamp. All calls to
* {@link #now now()} will always return this seed Date.
*
* @param now the specified Date to always return from all calls to {@link #now now()}.
*/
public FixedClock(Date now) {
this.now = now;
}
@Override
public Date now() {
return this.now;
}
}

View File

@ -15,6 +15,8 @@
*/ */
package io.jsonwebtoken package io.jsonwebtoken
import io.jsonwebtoken.impl.DefaultClock
import io.jsonwebtoken.impl.FixedClock
import io.jsonwebtoken.impl.TextCodec import io.jsonwebtoken.impl.TextCodec
import org.junit.Test import org.junit.Test
@ -1392,4 +1394,39 @@ class JwtParserTest {
) )
} }
} }
@Test
void testParseClockManipulationWithFixedClock() {
def then = System.currentTimeMillis() - 1000
Date expiry = new Date(then)
Date beforeExpiry = new Date(then - 1000)
String compact = Jwts.builder().setSubject('Joe').setExpiration(expiry).compact()
Jwts.parser().setClock(new FixedClock(beforeExpiry)).parse(compact)
}
@Test
void testParseClockManipulationWithNullClock() {
JwtParser parser = Jwts.parser();
try {
parser.setClock(null)
fail()
} catch (IllegalArgumentException expected) {
}
}
@Test
void testParseClockManipulationWithDefaultClock() {
Date expiry = new Date(System.currentTimeMillis() - 1000)
String compact = Jwts.builder().setSubject('Joe').setExpiration(expiry).compact()
try {
Jwts.parser().setClock(new DefaultClock()).parse(compact)
fail()
} catch (ExpiredJwtException e) {
assertTrue e.getMessage().startsWith('JWT expired at ')
}
}
} }

View File

@ -0,0 +1,19 @@
package io.jsonwebtoken.impl
import org.junit.Test
import static org.junit.Assert.*
class FixedClockTest {
@Test
void testFixedClockDefaultConstructor() {
def clock = new FixedClock()
def date1 = clock.now()
Thread.sleep(100)
def date2 = clock.now()
assertSame date1, date2
}
}