From 83054a755d6533d2ac143ad066a9309a7d781bf7 Mon Sep 17 00:00:00 2001 From: Mitchell Morris Date: Tue, 23 Feb 2016 14:43:32 -0600 Subject: [PATCH 1/3] allow the injection of a time source --- src/main/java/io/jsonwebtoken/JwtParser.java | 9 +++++++++ .../jsonwebtoken/impl/DefaultJwtParser.java | 20 ++++++++++++------- .../io/jsonwebtoken/JwtParserTest.groovy | 11 ++++++++++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java index 11e8a5f6..1c06a093 100644 --- a/src/main/java/io/jsonwebtoken/JwtParser.java +++ b/src/main/java/io/jsonwebtoken/JwtParser.java @@ -124,6 +124,15 @@ public interface JwtParser { */ JwtParser require(String claimName, Object value); + /** + * Replaces the system time-of-day call to return this fixed value instead. A {@code null} value will remove + * the fixed date output and return the system time-of-day instead. + * + * @param now the time-of-day to use for parsimg this JWT or {@code null} to use the current time-of-day + * @return the builder instance for method chaining. + */ + JwtParser setFixedClock(Date now); + /** * 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. diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index a5d68aec..076440ac 100644 --- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -69,6 +69,8 @@ public class DefaultJwtParser implements JwtParser { Claims expectedClaims = new DefaultClaims(); + private Date now = new Date(); + @Override public JwtParser requireIssuedAt(Date issuedAt) { expectedClaims.setIssuedAt(issuedAt); @@ -127,6 +129,17 @@ public class DefaultJwtParser implements JwtParser { return this; } + @Override + public JwtParser setFixedClock(Date now) { + if (now == null) { + this.now = new Date(); + } else { + this.now = now; + } + + return this; + } + @Override public JwtParser setSigningKey(byte[] key) { Assert.notEmpty(key, "signing key cannot be null or empty."); @@ -346,7 +359,6 @@ public class DefaultJwtParser implements JwtParser { //since 0.3: if (claims != null) { - Date now = null; SimpleDateFormat sdf; //https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4.1.4 @@ -354,8 +366,6 @@ public class DefaultJwtParser implements JwtParser { Date exp = claims.getExpiration(); if (exp != null) { - now = new Date(); - if (now.equals(exp) || now.after(exp)) { sdf = new SimpleDateFormat(ISO_8601_FORMAT); String expVal = sdf.format(exp); @@ -371,10 +381,6 @@ public class DefaultJwtParser implements JwtParser { Date nbf = claims.getNotBefore(); if (nbf != null) { - if (now == null) { - now = new Date(); - } - if (now.before(nbf)) { sdf = new SimpleDateFormat(ISO_8601_FORMAT); String nbfVal = sdf.format(nbf); diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy index 3e37658f..1a93bda3 100644 --- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy +++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy @@ -1392,4 +1392,15 @@ class JwtParserTest { ) } } + + @Test + void testParseClockManipulation() { + 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().setFixedClock(beforeExpiry).parse(compact) + } } From a20c92c0954c2e294eca61cc9be77bdcdde3477d Mon Sep 17 00:00:00 2001 From: Mitchell Morris Date: Tue, 23 Feb 2016 19:30:20 -0600 Subject: [PATCH 2/3] create a new Interface "Clock" plus implementations of Clock to exhibit desired behavior --- src/main/java/io/jsonwebtoken/Clock.java | 7 ++++ .../java/io/jsonwebtoken/DefaultClock.java | 10 ++++++ src/main/java/io/jsonwebtoken/JwtParser.java | 8 ++--- .../jsonwebtoken/impl/DefaultJwtParser.java | 14 +++++--- .../java/io/jsonwebtoken/impl/FixedClock.java | 22 +++++++++++++ .../io/jsonwebtoken/JwtParserTest.groovy | 33 +++++++++++++++++-- 6 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 src/main/java/io/jsonwebtoken/Clock.java create mode 100644 src/main/java/io/jsonwebtoken/DefaultClock.java create mode 100644 src/main/java/io/jsonwebtoken/impl/FixedClock.java diff --git a/src/main/java/io/jsonwebtoken/Clock.java b/src/main/java/io/jsonwebtoken/Clock.java new file mode 100644 index 00000000..69d6547b --- /dev/null +++ b/src/main/java/io/jsonwebtoken/Clock.java @@ -0,0 +1,7 @@ +package io.jsonwebtoken; + +import java.util.Date; + +public interface Clock { + Date now(); +} diff --git a/src/main/java/io/jsonwebtoken/DefaultClock.java b/src/main/java/io/jsonwebtoken/DefaultClock.java new file mode 100644 index 00000000..ddd47bc6 --- /dev/null +++ b/src/main/java/io/jsonwebtoken/DefaultClock.java @@ -0,0 +1,10 @@ +package io.jsonwebtoken; + +import java.util.Date; + +public class DefaultClock implements Clock { + @Override + public Date now() { + return new Date(); + } +} diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java index 1c06a093..58caee3b 100644 --- a/src/main/java/io/jsonwebtoken/JwtParser.java +++ b/src/main/java/io/jsonwebtoken/JwtParser.java @@ -125,13 +125,13 @@ public interface JwtParser { JwtParser require(String claimName, Object value); /** - * Replaces the system time-of-day call to return this fixed value instead. A {@code null} value will remove - * the fixed date output and return the system time-of-day instead. + * Replace the {@code clock} used by the parser to determine the current time-of-day to use when validating + * the parsed JWT. If {@code null}, will reset the behavior to use the system clock. * - * @param now the time-of-day to use for parsimg this JWT or {@code null} to use the current time-of-day + * @param clock a {@code Clock} object to return the time-of-day or {@code null} * @return the builder instance for method chaining. */ - JwtParser setFixedClock(Date now); + JwtParser setClock(Clock clock); /** * Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index 076440ac..8c4c7947 100644 --- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -18,8 +18,10 @@ package io.jsonwebtoken.impl; import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.ClaimJwtException; import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Clock; import io.jsonwebtoken.CompressionCodec; import io.jsonwebtoken.CompressionCodecResolver; +import io.jsonwebtoken.DefaultClock; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Header; import io.jsonwebtoken.IncorrectClaimException; @@ -69,7 +71,7 @@ public class DefaultJwtParser implements JwtParser { Claims expectedClaims = new DefaultClaims(); - private Date now = new Date(); + private Clock clock = new DefaultClock(); @Override public JwtParser requireIssuedAt(Date issuedAt) { @@ -130,11 +132,11 @@ public class DefaultJwtParser implements JwtParser { } @Override - public JwtParser setFixedClock(Date now) { - if (now == null) { - this.now = new Date(); + public JwtParser setClock(Clock clock) { + if (clock == null) { + this.clock = new DefaultClock(); } else { - this.now = now; + this.clock = clock; } return this; @@ -361,6 +363,8 @@ public class DefaultJwtParser implements JwtParser { SimpleDateFormat sdf; + final Date now = this.clock.now(); + //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: Date exp = claims.getExpiration(); diff --git a/src/main/java/io/jsonwebtoken/impl/FixedClock.java b/src/main/java/io/jsonwebtoken/impl/FixedClock.java new file mode 100644 index 00000000..83007370 --- /dev/null +++ b/src/main/java/io/jsonwebtoken/impl/FixedClock.java @@ -0,0 +1,22 @@ +package io.jsonwebtoken.impl; + +import io.jsonwebtoken.Clock; + +import java.util.Date; + +public class FixedClock implements Clock { + private final Date now; + + public FixedClock() { + this(new Date()); + } + + public FixedClock(Date now) { + this.now = now; + } + + @Override + public Date now() { + return this.now; + } +} diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy index 1a93bda3..c5451cf2 100644 --- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy +++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy @@ -15,6 +15,7 @@ */ package io.jsonwebtoken +import io.jsonwebtoken.impl.FixedClock import io.jsonwebtoken.impl.TextCodec import org.junit.Test @@ -1394,13 +1395,41 @@ class JwtParserTest { } @Test - void testParseClockManipulation() { + 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().setFixedClock(beforeExpiry).parse(compact) + Jwts.parser().setClock(new FixedClock(beforeExpiry)).parse(compact) + } + + @Test + void testParseClockManipulationWithNullClock() { + Date expiry = new Date(System.currentTimeMillis() - 1000) + + String compact = Jwts.builder().setSubject('Joe').setExpiration(expiry).compact() + + try { + Jwts.parser().setClock(null).parse(compact) + fail() + } catch (ExpiredJwtException e) { + assertTrue e.getMessage().startsWith('JWT expired at ') + } + } + + @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 ') + } } } From 72e0e3b23c4f363fd367220945e5549c8f3564d3 Mon Sep 17 00:00:00 2001 From: Les Hazlewood Date: Fri, 1 Apr 2016 18:15:37 -0700 Subject: [PATCH 3/3] 109: enabled injection of a time source - a 'Clock' --- src/main/java/io/jsonwebtoken/Clock.java | 15 ++++++++++++ .../java/io/jsonwebtoken/DefaultClock.java | 10 -------- src/main/java/io/jsonwebtoken/JwtParser.java | 9 +++++--- .../io/jsonwebtoken/impl/DefaultClock.java | 23 +++++++++++++++++++ .../jsonwebtoken/impl/DefaultJwtParser.java | 11 +++------ .../java/io/jsonwebtoken/impl/FixedClock.java | 17 ++++++++++++++ .../io/jsonwebtoken/JwtParserTest.groovy | 11 ++++----- .../jsonwebtoken/impl/FixedClockTest.groovy | 19 +++++++++++++++ 8 files changed, 87 insertions(+), 28 deletions(-) delete mode 100644 src/main/java/io/jsonwebtoken/DefaultClock.java create mode 100644 src/main/java/io/jsonwebtoken/impl/DefaultClock.java create mode 100644 src/test/groovy/io/jsonwebtoken/impl/FixedClockTest.groovy diff --git a/src/main/java/io/jsonwebtoken/Clock.java b/src/main/java/io/jsonwebtoken/Clock.java index 69d6547b..7e57df80 100644 --- a/src/main/java/io/jsonwebtoken/Clock.java +++ b/src/main/java/io/jsonwebtoken/Clock.java @@ -1,7 +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(); } diff --git a/src/main/java/io/jsonwebtoken/DefaultClock.java b/src/main/java/io/jsonwebtoken/DefaultClock.java deleted file mode 100644 index ddd47bc6..00000000 --- a/src/main/java/io/jsonwebtoken/DefaultClock.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.jsonwebtoken; - -import java.util.Date; - -public class DefaultClock implements Clock { - @Override - public Date now() { - return new Date(); - } -} diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java index 58caee3b..e0373093 100644 --- a/src/main/java/io/jsonwebtoken/JwtParser.java +++ b/src/main/java/io/jsonwebtoken/JwtParser.java @@ -15,6 +15,8 @@ */ package io.jsonwebtoken; +import io.jsonwebtoken.impl.DefaultClock; + import java.security.Key; import java.util.Date; @@ -125,11 +127,12 @@ public interface JwtParser { JwtParser require(String claimName, Object value); /** - * Replace the {@code clock} used by the parser to determine the current time-of-day to use when validating - * the parsed JWT. If {@code null}, will reset the behavior to use the system clock. + * 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 time-of-day or {@code null} + * @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); diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultClock.java b/src/main/java/io/jsonwebtoken/impl/DefaultClock.java new file mode 100644 index 00000000..14d1eed7 --- /dev/null +++ b/src/main/java/io/jsonwebtoken/impl/DefaultClock.java @@ -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 new {@link Date}(). + * + * @return a new {@link Date} instance. + */ + @Override + public Date now() { + return new Date(); + } +} diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index 8c4c7947..8f8c02c8 100644 --- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -21,7 +21,6 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Clock; import io.jsonwebtoken.CompressionCodec; import io.jsonwebtoken.CompressionCodecResolver; -import io.jsonwebtoken.DefaultClock; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Header; import io.jsonwebtoken.IncorrectClaimException; @@ -71,7 +70,7 @@ public class DefaultJwtParser implements JwtParser { Claims expectedClaims = new DefaultClaims(); - private Clock clock = new DefaultClock(); + private Clock clock = Clock.DEFAULT; @Override public JwtParser requireIssuedAt(Date issuedAt) { @@ -133,12 +132,8 @@ public class DefaultJwtParser implements JwtParser { @Override public JwtParser setClock(Clock clock) { - if (clock == null) { - this.clock = new DefaultClock(); - } else { - this.clock = clock; - } - + Assert.notNull(clock, "Clock instance cannot be null."); + this.clock = clock; return this; } diff --git a/src/main/java/io/jsonwebtoken/impl/FixedClock.java b/src/main/java/io/jsonwebtoken/impl/FixedClock.java index 83007370..88de45dc 100644 --- a/src/main/java/io/jsonwebtoken/impl/FixedClock.java +++ b/src/main/java/io/jsonwebtoken/impl/FixedClock.java @@ -4,13 +4,30 @@ 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 new {@link Date Date}() 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; } diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy index c5451cf2..40dba0c7 100644 --- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy +++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy @@ -15,6 +15,7 @@ */ package io.jsonwebtoken +import io.jsonwebtoken.impl.DefaultClock import io.jsonwebtoken.impl.FixedClock import io.jsonwebtoken.impl.TextCodec import org.junit.Test @@ -1407,15 +1408,11 @@ class JwtParserTest { @Test void testParseClockManipulationWithNullClock() { - Date expiry = new Date(System.currentTimeMillis() - 1000) - - String compact = Jwts.builder().setSubject('Joe').setExpiration(expiry).compact() - + JwtParser parser = Jwts.parser(); try { - Jwts.parser().setClock(null).parse(compact) + parser.setClock(null) fail() - } catch (ExpiredJwtException e) { - assertTrue e.getMessage().startsWith('JWT expired at ') + } catch (IllegalArgumentException expected) { } } diff --git a/src/test/groovy/io/jsonwebtoken/impl/FixedClockTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/FixedClockTest.groovy new file mode 100644 index 00000000..fc092e6a --- /dev/null +++ b/src/test/groovy/io/jsonwebtoken/impl/FixedClockTest.groovy @@ -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 + } +}