From 83054a755d6533d2ac143ad066a9309a7d781bf7 Mon Sep 17 00:00:00 2001 From: Mitchell Morris Date: Tue, 23 Feb 2016 14:43:32 -0600 Subject: [PATCH 1/2] 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/2] 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 ') + } } }