diff --git a/.travis.yml b/.travis.yml index 3f4905b9e..f212cd8b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ dist: trusty sudo: required language: java jdk: - - openjdk7 - oraclejdk8 - oraclejdk9 diff --git a/pom.xml b/pom.xml index 1c34c5b55..1dbd07d54 100644 --- a/pom.xml +++ b/pom.xml @@ -109,6 +109,12 @@ ${jackson.version} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + + org.bouncycastle diff --git a/src/main/java/io/jsonwebtoken/Claims.java b/src/main/java/io/jsonwebtoken/Claims.java index 67fb44867..f168499af 100644 --- a/src/main/java/io/jsonwebtoken/Claims.java +++ b/src/main/java/io/jsonwebtoken/Claims.java @@ -15,7 +15,7 @@ */ package io.jsonwebtoken; -import java.util.Date; +import java.time.Instant; import java.util.Map; /** @@ -111,13 +111,13 @@ public interface Claims extends Map, ClaimsMutator { * * @return the JWT {@code exp} value or {@code null} if not present. */ - Date getExpiration(); + Instant getExpiration(); /** * {@inheritDoc} */ @Override //only for better/targeted JavaDoc - Claims setExpiration(Date exp); + Claims setExpiration(Instant exp); /** * Returns the JWT @@ -127,13 +127,13 @@ public interface Claims extends Map, ClaimsMutator { * * @return the JWT {@code nbf} value or {@code null} if not present. */ - Date getNotBefore(); + Instant getNotBefore(); /** * {@inheritDoc} */ @Override //only for better/targeted JavaDoc - Claims setNotBefore(Date nbf); + Claims setNotBefore(Instant nbf); /** * Returns the JWT @@ -143,13 +143,13 @@ public interface Claims extends Map, ClaimsMutator { * * @return the JWT {@code nbf} value or {@code null} if not present. */ - Date getIssuedAt(); + Instant getIssuedAt(); /** * {@inheritDoc} */ @Override //only for better/targeted JavaDoc - Claims setIssuedAt(Date iat); + Claims setIssuedAt(Instant iat); /** * Returns the JWTs diff --git a/src/main/java/io/jsonwebtoken/ClaimsMutator.java b/src/main/java/io/jsonwebtoken/ClaimsMutator.java index 66528d8ff..303323cf6 100644 --- a/src/main/java/io/jsonwebtoken/ClaimsMutator.java +++ b/src/main/java/io/jsonwebtoken/ClaimsMutator.java @@ -15,7 +15,7 @@ */ package io.jsonwebtoken; -import java.util.Date; +import java.time.Instant; /** * Mutation (modifications) to a {@link io.jsonwebtoken.Claims Claims} instance. @@ -63,7 +63,7 @@ public interface ClaimsMutator { * @param exp the JWT {@code exp} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. */ - T setExpiration(Date exp); + T setExpiration(Instant exp); /** * Sets the JWT @@ -74,7 +74,7 @@ public interface ClaimsMutator { * @param nbf the JWT {@code nbf} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. */ - T setNotBefore(Date nbf); + T setNotBefore(Instant nbf); /** * Sets the JWT @@ -85,7 +85,7 @@ public interface ClaimsMutator { * @param iat the JWT {@code iat} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. */ - T setIssuedAt(Date iat); + T setIssuedAt(Instant iat); /** * Sets the JWT diff --git a/src/main/java/io/jsonwebtoken/Clock.java b/src/main/java/io/jsonwebtoken/Clock.java index 9e98d02b3..37eda4d4f 100644 --- a/src/main/java/io/jsonwebtoken/Clock.java +++ b/src/main/java/io/jsonwebtoken/Clock.java @@ -1,6 +1,6 @@ package io.jsonwebtoken; -import java.util.Date; +import java.time.Instant; /** * A clock represents a time source that can be used when creating and verifying JWTs. @@ -14,5 +14,5 @@ public interface Clock { * * @return the clock's current timestamp at the instant the method is invoked. */ - Date now(); + Instant now(); } diff --git a/src/main/java/io/jsonwebtoken/JwtBuilder.java b/src/main/java/io/jsonwebtoken/JwtBuilder.java index 5626218cc..70cfdee51 100644 --- a/src/main/java/io/jsonwebtoken/JwtBuilder.java +++ b/src/main/java/io/jsonwebtoken/JwtBuilder.java @@ -16,7 +16,7 @@ package io.jsonwebtoken; import java.security.Key; -import java.util.Date; +import java.time.Instant; import java.util.Map; /** @@ -198,16 +198,16 @@ public interface JwtBuilder extends ClaimsMutator { *

A JWT obtained after this timestamp should not be used.

* *

This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set - * the Claims {@link Claims#setExpiration(java.util.Date) expiration} field with the specified value. This allows + * the Claims {@link Claims#setExpiration(java.time.Instant) expiration} field with the specified value. This allows * you to write code like this:

* *
-     * String jwt = Jwts.builder().setExpiration(new Date(System.currentTimeMillis() + 3600000)).compact();
+     * String jwt = Jwts.builder().setExpiration(Instant.ofEpochMilli(System.currentTimeMillis() + 3600000)).compact();
      * 
* *

instead of this:

*
-     * Claims claims = Jwts.claims().setExpiration(new Date(System.currentTimeMillis() + 3600000));
+     * Claims claims = Jwts.claims().setExpiration(Instant.ofEpochMilli(System.currentTimeMillis() + 3600000));
      * String jwt = Jwts.builder().setClaims(claims).compact();
      * 
*

if desired.

@@ -217,7 +217,7 @@ public interface JwtBuilder extends ClaimsMutator { * @since 0.2 */ @Override //only for better/targeted JavaDoc - JwtBuilder setExpiration(Date exp); + JwtBuilder setExpiration(Instant exp); /** * Sets the JWT Claims
@@ -226,16 +226,16 @@ public interface JwtBuilder extends ClaimsMutator { *

A JWT obtained before this timestamp should not be used.

* *

This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set - * the Claims {@link Claims#setNotBefore(java.util.Date) notBefore} field with the specified value. This allows + * the Claims {@link Claims#setNotBefore(java.time.Instant) notBefore} field with the specified value. This allows * you to write code like this:

* *
-     * String jwt = Jwts.builder().setNotBefore(new Date()).compact();
+     * String jwt = Jwts.builder().setNotBefore(Instant.now()).compact();
      * 
* *

instead of this:

*
-     * Claims claims = Jwts.claims().setNotBefore(new Date());
+     * Claims claims = Jwts.claims().setNotBefore(Instant.now());
      * String jwt = Jwts.builder().setClaims(claims).compact();
      * 
*

if desired.

@@ -245,7 +245,7 @@ public interface JwtBuilder extends ClaimsMutator { * @since 0.2 */ @Override //only for better/targeted JavaDoc - JwtBuilder setNotBefore(Date nbf); + JwtBuilder setNotBefore(Instant nbf); /** * Sets the JWT Claims
@@ -254,7 +254,7 @@ public interface JwtBuilder extends ClaimsMutator { *

The value is the timestamp when the JWT was created.

* *

This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set - * the Claims {@link Claims#setIssuedAt(java.util.Date) issuedAt} field with the specified value. This allows + * the Claims {@link Claims#setIssuedAt(java.time.Instant) issuedAt} field with the specified value. This allows * you to write code like this:

* *
@@ -263,7 +263,7 @@ public interface JwtBuilder extends ClaimsMutator {
      *
      * 

instead of this:

*
-     * Claims claims = Jwts.claims().setIssuedAt(new Date());
+     * Claims claims = Jwts.claims().setIssuedAt(Instant.now());
      * String jwt = Jwts.builder().setClaims(claims).compact();
      * 
*

if desired.

@@ -273,7 +273,7 @@ public interface JwtBuilder extends ClaimsMutator { * @since 0.2 */ @Override //only for better/targeted JavaDoc - JwtBuilder setIssuedAt(Date iat); + JwtBuilder setIssuedAt(Instant iat); /** * Sets the JWT Claims
diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java index 1dcdcf58f..e2a2380f1 100644 --- a/src/main/java/io/jsonwebtoken/JwtParser.java +++ b/src/main/java/io/jsonwebtoken/JwtParser.java @@ -18,7 +18,7 @@ import io.jsonwebtoken.impl.DefaultClock; import java.security.Key; -import java.util.Date; +import java.time.Instant; /** * A parser for reading JWT strings, used to convert them into a {@link Jwt} object representing the expanded JWT. @@ -87,7 +87,7 @@ public interface JwtParser { * @see MissingClaimException * @see IncorrectClaimException */ - JwtParser requireIssuedAt(Date issuedAt); + JwtParser requireIssuedAt(Instant issuedAt); /** * Ensures that the specified {@code exp} exists in the parsed JWT. If missing or if the parsed @@ -99,7 +99,7 @@ public interface JwtParser { * @see MissingClaimException * @see IncorrectClaimException */ - JwtParser requireExpiration(Date expiration); + JwtParser requireExpiration(Instant expiration); /** * Ensures that the specified {@code nbf} exists in the parsed JWT. If missing or if the parsed @@ -111,7 +111,7 @@ public interface JwtParser { * @see MissingClaimException * @see IncorrectClaimException */ - JwtParser requireNotBefore(Date notBefore); + JwtParser requireNotBefore(Instant notBefore); /** * Ensures that the specified {@code claimName} exists in the parsed JWT. If missing or if the parsed diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java b/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java index 2523d305a..6afe7333a 100644 --- a/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java +++ b/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java @@ -18,7 +18,7 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.RequiredTypeException; -import java.util.Date; +import java.time.Instant; import java.util.Map; public class DefaultClaims extends JwtMap implements Claims { @@ -65,35 +65,35 @@ public Claims setAudience(String aud) { } @Override - public Date getExpiration() { - return get(Claims.EXPIRATION, Date.class); + public Instant getExpiration() { + return get(Claims.EXPIRATION, Instant.class); } @Override - public Claims setExpiration(Date exp) { - setDate(Claims.EXPIRATION, exp); + public Claims setExpiration(Instant exp) { + setInstant(Claims.EXPIRATION, exp); return this; } @Override - public Date getNotBefore() { - return get(Claims.NOT_BEFORE, Date.class); + public Instant getNotBefore() { + return get(Claims.NOT_BEFORE, Instant.class); } @Override - public Claims setNotBefore(Date nbf) { - setDate(Claims.NOT_BEFORE, nbf); + public Claims setNotBefore(Instant nbf) { + setInstant(Claims.NOT_BEFORE, nbf); return this; } @Override - public Date getIssuedAt() { - return get(Claims.ISSUED_AT, Date.class); + public Instant getIssuedAt() { + return get(Claims.ISSUED_AT, Instant.class); } @Override - public Claims setIssuedAt(Date iat) { - setDate(Claims.ISSUED_AT, iat); + public Claims setIssuedAt(Instant iat) { + setInstant(Claims.ISSUED_AT, iat); return this; } @@ -117,15 +117,15 @@ public T get(String claimName, Class requiredType) { Claims.ISSUED_AT.equals(claimName) || Claims.NOT_BEFORE.equals(claimName) ) { - value = getDate(claimName); + value = getInstant(claimName); } return castClaimValue(value, requiredType); } private T castClaimValue(Object value, Class requiredType) { - if (requiredType == Date.class && value instanceof Long) { - value = new Date((Long)value); + if (requiredType == Instant.class && value instanceof Number) { + value = Instant.ofEpochSecond(Long.parseLong(value.toString())); } if (value instanceof Integer) { diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultClock.java b/src/main/java/io/jsonwebtoken/impl/DefaultClock.java index 0bf8171cc..e09014748 100644 --- a/src/main/java/io/jsonwebtoken/impl/DefaultClock.java +++ b/src/main/java/io/jsonwebtoken/impl/DefaultClock.java @@ -2,7 +2,7 @@ import io.jsonwebtoken.Clock; -import java.util.Date; +import java.time.Instant; /** * Default {@link Clock} implementation. @@ -17,12 +17,12 @@ public class DefaultClock implements Clock { public static final Clock INSTANCE = new DefaultClock(); /** - * Simply returns new {@link Date}(). + * Simply returns new {@link Instant}(). * - * @return a new {@link Date} instance. + * @return a new {@link Instant} instance. */ @Override - public Date now() { - return new Date(); + public Instant now() { + return Instant.now(); } } diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java index 25b48d62f..7a0a4ad8c 100644 --- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java +++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java @@ -15,8 +15,13 @@ */ package io.jsonwebtoken.impl; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import io.jsonwebtoken.*; import io.jsonwebtoken.impl.crypto.DefaultJwtSigner; import io.jsonwebtoken.impl.crypto.JwtSigner; @@ -26,13 +31,27 @@ import io.jsonwebtoken.lang.Strings; import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; import java.security.Key; -import java.util.Date; +import java.time.Instant; import java.util.Map; public class DefaultJwtBuilder implements JwtBuilder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER; + + static { + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(Instant.class, new JsonSerializer() { + @Override + public void serialize(Instant instant, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException { + // The JWT RFC *mandates* time claim values are represented as seconds. + jsonGenerator.writeNumber(instant.getEpochSecond()); + } + }); + OBJECT_MAPPER = new ObjectMapper().registerModule(javaTimeModule) + .configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false); + } private Header header; private Claims claims; @@ -184,7 +203,7 @@ public JwtBuilder setAudience(String aud) { } @Override - public JwtBuilder setExpiration(Date exp) { + public JwtBuilder setExpiration(Instant exp) { if (exp != null) { ensureClaims().setExpiration(exp); } else { @@ -197,7 +216,7 @@ public JwtBuilder setExpiration(Date exp) { } @Override - public JwtBuilder setNotBefore(Date nbf) { + public JwtBuilder setNotBefore(Instant nbf) { if (nbf != null) { ensureClaims().setNotBefore(nbf); } else { @@ -210,7 +229,7 @@ public JwtBuilder setNotBefore(Date nbf) { } @Override - public JwtBuilder setIssuedAt(Date iat) { + public JwtBuilder setIssuedAt(Instant iat) { if (iat != null) { ensureClaims().setIssuedAt(iat); } else { diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index 4e4b9c79c..345bea116 100644 --- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -16,6 +16,7 @@ package io.jsonwebtoken.impl; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import io.jsonwebtoken.ClaimJwtException; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Clock; @@ -48,18 +49,15 @@ import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.security.Key; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.time.Instant; +import java.time.format.DateTimeFormatter; import java.util.Map; +import java.util.concurrent.TimeUnit; @SuppressWarnings("unchecked") public class DefaultJwtParser implements JwtParser { - //don't need millis since JWT date fields are only second granularity: - private static final String ISO_8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; - private static final int MILLISECONDS_PER_SECOND = 1000; - - private ObjectMapper objectMapper = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().registerModule(new JavaTimeModule()); private byte[] keyBytes; @@ -69,14 +67,14 @@ public class DefaultJwtParser implements JwtParser { private CompressionCodecResolver compressionCodecResolver = new DefaultCompressionCodecResolver(); - Claims expectedClaims = new DefaultClaims(); + private Claims expectedClaims = new DefaultClaims(); private Clock clock = DefaultClock.INSTANCE; private long allowedClockSkewMillis = 0; @Override - public JwtParser requireIssuedAt(Date issuedAt) { + public JwtParser requireIssuedAt(Instant issuedAt) { expectedClaims.setIssuedAt(issuedAt); return this; } @@ -106,13 +104,13 @@ public JwtParser requireId(String id) { } @Override - public JwtParser requireExpiration(Date expiration) { + public JwtParser requireExpiration(Instant expiration) { expectedClaims.setExpiration(expiration); return this; } @Override - public JwtParser requireNotBefore(Date notBefore) { + public JwtParser requireNotBefore(Instant notBefore) { expectedClaims.setNotBefore(notBefore); return this; } @@ -134,7 +132,7 @@ public JwtParser setClock(Clock clock) { @Override public JwtParser setAllowedClockSkewSeconds(long seconds) { - this.allowedClockSkewMillis = Math.max(0, seconds * MILLISECONDS_PER_SECOND); + this.allowedClockSkewMillis = Math.max(0, TimeUnit.SECONDS.toMillis(seconds)); return this; } @@ -360,24 +358,22 @@ public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, //since 0.3: if (claims != null) { - SimpleDateFormat sdf; - - final Date now = this.clock.now(); - long nowTime = now.getTime(); + final Instant now = this.clock.now(); + final long nowEpochMillis = now.toEpochMilli(); //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(); + Instant exp = claims.getExpiration(); if (exp != null) { - long maxTime = nowTime - this.allowedClockSkewMillis; - Date max = allowSkew ? new Date(maxTime) : now; - if (max.after(exp)) { - sdf = new SimpleDateFormat(ISO_8601_FORMAT); - String expVal = sdf.format(exp); - String nowVal = sdf.format(now); + final long maxTime = nowEpochMillis - this.allowedClockSkewMillis; + Instant max = allowSkew ? Instant.ofEpochMilli(maxTime) : now; + if (max.isAfter(exp)) { + // This returns an immutable formatter capable of formatting and parsing the ISO-8601 instant format. + String expVal = DateTimeFormatter.ISO_INSTANT.format(exp); + String nowVal = DateTimeFormatter.ISO_INSTANT.format(now); - long differenceMillis = maxTime - exp.getTime(); + long differenceMillis = maxTime - exp.toEpochMilli(); String msg = "JWT expired at " + expVal + ". Current time: " + nowVal + ", a difference of " + differenceMillis + " milliseconds. Allowed clock skew: " + @@ -388,17 +384,17 @@ public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, //https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4.1.5 //token MUST NOT be accepted before any specified nbf time: - Date nbf = claims.getNotBefore(); + Instant nbf = claims.getNotBefore(); if (nbf != null) { - long minTime = nowTime + this.allowedClockSkewMillis; - Date min = allowSkew ? new Date(minTime) : now; - if (min.before(nbf)) { - sdf = new SimpleDateFormat(ISO_8601_FORMAT); - String nbfVal = sdf.format(nbf); - String nowVal = sdf.format(now); + final long minTime = nowEpochMillis + this.allowedClockSkewMillis; + Instant min = allowSkew ? Instant.ofEpochMilli(minTime) : now; + if (min.isBefore(nbf)) { + // This returns an immutable formatter capable of formatting and parsing the ISO-8601 instant format. + String nbfVal = DateTimeFormatter.ISO_INSTANT.format(nbf); + String nowVal = DateTimeFormatter.ISO_INSTANT.format(now); - long differenceMillis = nbf.getTime() - minTime; + long differenceMillis = nbf.toEpochMilli() - minTime; String msg = "JWT must not be accepted before " + nbfVal + ". Current time: " + nowVal + ", a difference of " + @@ -431,14 +427,14 @@ private void validateExpectedClaims(Header header, Claims claims) { Claims.EXPIRATION.equals(expectedClaimName) || Claims.NOT_BEFORE.equals(expectedClaimName) ) { - expectedClaimValue = expectedClaims.get(expectedClaimName, Date.class); - actualClaimValue = claims.get(expectedClaimName, Date.class); + expectedClaimValue = expectedClaims.get(expectedClaimName, Instant.class); + actualClaimValue = claims.get(expectedClaimName, Instant.class); } else if ( - expectedClaimValue instanceof Date && + expectedClaimValue instanceof Instant && actualClaimValue != null && - actualClaimValue instanceof Long + actualClaimValue instanceof Number ) { - actualClaimValue = new Date((Long)actualClaimValue); + actualClaimValue = Instant.ofEpochSecond(Long.parseLong(actualClaimValue.toString())); } InvalidClaimException invalidClaimException = null; @@ -549,7 +545,7 @@ public Jws onClaimsJws(Jws jws) { @SuppressWarnings("unchecked") protected Map readValue(String val) { try { - return objectMapper.readValue(val, Map.class); + return OBJECT_MAPPER.readValue(val, Map.class); } catch (IOException e) { throw new MalformedJwtException("Unable to read JSON value: " + val, e); } diff --git a/src/main/java/io/jsonwebtoken/impl/FixedClock.java b/src/main/java/io/jsonwebtoken/impl/FixedClock.java index 88de45dcb..3550f21b5 100644 --- a/src/main/java/io/jsonwebtoken/impl/FixedClock.java +++ b/src/main/java/io/jsonwebtoken/impl/FixedClock.java @@ -2,7 +2,7 @@ import io.jsonwebtoken.Clock; -import java.util.Date; +import java.time.Instant; /** * A {@code Clock} implementation that is constructed with a seed timestamp and always reports that same @@ -12,28 +12,28 @@ */ public class FixedClock implements Clock { - private final Date now; + private final Instant now; /** - * Creates a new fixed clock using new {@link Date Date}() as the seed timestamp. All calls to + * Creates a new fixed clock using new {@link Instant instant}() as the seed timestamp. All calls to * {@link #now now()} will always return this seed Date. */ public FixedClock() { - this(new Date()); + this(Instant.now()); } /** * Creates a new fixed clock using the specified seed timestamp. All calls to - * {@link #now now()} will always return this seed Date. + * {@link #now now()} will always return this seed Instant. * - * @param now the specified Date to always return from all calls to {@link #now now()}. + * @param now the specified Instant to always return from all calls to {@link #now now()}. */ - public FixedClock(Date now) { + public FixedClock(Instant now) { this.now = now; } @Override - public Date now() { + public Instant now() { return this.now; } } diff --git a/src/main/java/io/jsonwebtoken/impl/JwtMap.java b/src/main/java/io/jsonwebtoken/impl/JwtMap.java index 2c3329cbc..d7939cd92 100644 --- a/src/main/java/io/jsonwebtoken/impl/JwtMap.java +++ b/src/main/java/io/jsonwebtoken/impl/JwtMap.java @@ -17,8 +17,8 @@ import io.jsonwebtoken.lang.Assert; +import java.time.Instant; import java.util.Collection; -import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -41,27 +41,23 @@ protected String getString(String name) { return v != null ? String.valueOf(v) : null; } - protected static Date toDate(Object v, String name) { + protected static Instant toInstant(Object v, String name) { if (v == null) { return null; - } else if (v instanceof Date) { - return (Date) v; + } else if (v instanceof Instant) { + return (Instant) v; } else if (v instanceof Number) { // https://github.com/jwtk/jjwt/issues/122: // The JWT RFC *mandates* NumericDate values are represented as seconds. - // Because Because java.util.Date requires milliseconds, we need to multiply by 1000: long seconds = ((Number) v).longValue(); - long millis = seconds * 1000; - return new Date(millis); + return Instant.ofEpochSecond(seconds); } else if (v instanceof String) { // https://github.com/jwtk/jjwt/issues/122 // The JWT RFC *mandates* NumericDate values are represented as seconds. - // Because Because java.util.Date requires milliseconds, we need to multiply by 1000: long seconds = Long.parseLong((String) v); - long millis = seconds * 1000; - return new Date(millis); + return Instant.ofEpochSecond(seconds); } else { - throw new IllegalStateException("Cannot convert '" + name + "' value [" + v + "] to Date instance."); + throw new IllegalStateException("Cannot convert '" + name + "' value [" + v + "] to Instant instance."); } } @@ -73,17 +69,16 @@ protected void setValue(String name, Object v) { } } - protected Date getDate(String name) { + protected Instant getInstant(String name) { Object v = map.get(name); - return toDate(v, name); + return toInstant(v, name); } - protected void setDate(String name, Date d) { - if (d == null) { + protected void setInstant(String name, Instant instant) { + if (instant == null) { map.remove(name); } else { - long seconds = d.getTime() / 1000; - map.put(name, seconds); + map.put(name, instant.getEpochSecond()); } } diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy index 00dc67f23..f606fbe79 100644 --- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy +++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy @@ -22,6 +22,8 @@ import org.junit.Test import javax.crypto.spec.SecretKeySpec import java.security.SecureRandom +import java.time.Instant +import java.time.temporal.ChronoUnit import static org.junit.Assert.* import static ClaimJwtException.INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE @@ -29,12 +31,12 @@ import static ClaimJwtException.MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE class JwtParserTest { - private static final SecureRandom random = new SecureRandom(); //doesn't need to be seeded - just testing + private static final SecureRandom RANDOM = new SecureRandom(); //doesn't need to be seeded - just testing protected static byte[] randomKey() { //create random signing key for testing: byte[] key = new byte[64] - random.nextBytes(key) + RANDOM.nextBytes(key) return key } @@ -165,8 +167,7 @@ class JwtParserTest { @Test void testParseWithExpiredJwt() { - - Date exp = new Date(System.currentTimeMillis() - 1000) + Instant exp = instantFromMillis(System.currentTimeMillis() - 1000) String compact = Jwts.builder().setSubject('Joe').setExpiration(exp).compact() @@ -183,8 +184,7 @@ class JwtParserTest { @Test void testParseWithPrematureJwt() { - - Date nbf = new Date(System.currentTimeMillis() + 100000) + Instant nbf = instantFromMillis(System.currentTimeMillis() + 100000) String compact = Jwts.builder().setSubject('Joe').setNotBefore(nbf).compact() @@ -201,7 +201,7 @@ class JwtParserTest { @Test void testParseWithExpiredJwtWithinAllowedClockSkew() { - Date exp = new Date(System.currentTimeMillis() - 3000) + Instant exp = instantFromMillis(System.currentTimeMillis() - 3000) String subject = 'Joe' String compact = Jwts.builder().setSubject(subject).setExpiration(exp).compact() @@ -213,7 +213,7 @@ class JwtParserTest { @Test void testParseWithExpiredJwtNotWithinAllowedClockSkew() { - Date exp = new Date(System.currentTimeMillis() - 3000) + Instant exp = instantFromMillis(System.currentTimeMillis() - 3000) String compact = Jwts.builder().setSubject('Joe').setExpiration(exp).compact() @@ -227,7 +227,7 @@ class JwtParserTest { @Test void testParseWithPrematureJwtWithinAllowedClockSkew() { - Date exp = new Date(System.currentTimeMillis() + 3000) + Instant exp = instantFromMillis(System.currentTimeMillis() + 3000) String subject = 'Joe' String compact = Jwts.builder().setSubject(subject).setNotBefore(exp).compact() @@ -239,7 +239,7 @@ class JwtParserTest { @Test void testParseWithPrematureJwtNotWithinAllowedClockSkew() { - Date exp = new Date(System.currentTimeMillis() + 3000) + Instant exp = instantFromMillis(System.currentTimeMillis() + 3000) String compact = Jwts.builder().setSubject('Joe').setNotBefore(exp).compact() @@ -370,9 +370,8 @@ class JwtParserTest { @Test void testParseClaimsJwtWithExpiredJwt() { - long nowMillis = System.currentTimeMillis() //some time in the past: - Date exp = new Date(nowMillis - 1000) + Instant exp = instantFromMillis(System.currentTimeMillis() - 1000) String compact = Jwts.builder().setSubject('Joe').setExpiration(exp).compact() @@ -387,7 +386,7 @@ class JwtParserTest { @Test void testParseClaimsJwtWithPrematureJwt() { - Date nbf = new Date(System.currentTimeMillis() + 100000) + Instant nbf = instantFromMillis(System.currentTimeMillis() + 100000) String compact = Jwts.builder().setSubject('Joe').setNotBefore(nbf).compact() @@ -493,9 +492,8 @@ class JwtParserTest { byte[] key = randomKey() - long nowMillis = System.currentTimeMillis() //some time in the past: - Date exp = new Date(nowMillis - 1000) + Instant exp = instantFromMillis(System.currentTimeMillis() - 1000) String compact = Jwts.builder().setSubject(sub).signWith(SignatureAlgorithm.HS256, key).setExpiration(exp).compact() @@ -516,7 +514,7 @@ class JwtParserTest { byte[] key = randomKey() - Date nbf = new Date(System.currentTimeMillis() + 100000) + Instant nbf = instantFromMillis(System.currentTimeMillis() + 100000) String compact = Jwts.builder().setSubject(sub).setNotBefore(nbf).signWith(SignatureAlgorithm.HS256, key).compact() @@ -962,7 +960,7 @@ class JwtParserTest { @Test void testParseRequireIssuedAt_Success() { - def issuedAt = new Date(System.currentTimeMillis()) + Instant issuedAt = instantFromMillis(System.currentTimeMillis()) byte[] key = randomKey() @@ -975,15 +973,16 @@ class JwtParserTest { parseClaimsJws(compact) // system converts to seconds (lopping off millis precision), then returns millis - def issuedAtMillis = ((long)issuedAt.getTime() / 1000) * 1000 + def issuedAtMillis = issuedAt.toEpochMilli() - assertEquals jwt.getBody().getIssuedAt().getTime(), issuedAtMillis, 0 + assertEquals jwt.getBody().getIssuedAt().toEpochMilli(), issuedAtMillis, 0 } @Test void testParseRequireIssuedAt_Incorrect_Fail() { - def goodIssuedAt = new Date(System.currentTimeMillis()) - def badIssuedAt = new Date(System.currentTimeMillis() - 10000) + Instant goodIssuedAt = instantFromMillis(System.currentTimeMillis()) + + Instant badIssuedAt = instantFromMillis(System.currentTimeMillis() - 10000) byte[] key = randomKey() @@ -1006,7 +1005,7 @@ class JwtParserTest { @Test void testParseRequireIssuedAt_Missing_Fail() { - def issuedAt = new Date(System.currentTimeMillis() - 10000) + Instant issuedAt = instantFromMillis(System.currentTimeMillis() - 10000) byte[] key = randomKey() @@ -1286,7 +1285,7 @@ class JwtParserTest { @Test void testParseRequireExpiration_Success() { // expire in the future - def expiration = new Date(System.currentTimeMillis() + 10000) + Instant expiration = instantFromMillis(System.currentTimeMillis() + 10000) byte[] key = randomKey() @@ -1299,15 +1298,16 @@ class JwtParserTest { parseClaimsJws(compact) // system converts to seconds (lopping off millis precision), then returns millis - def expirationMillis = ((long)expiration.getTime() / 1000) * 1000 + def expirationMillis = expiration.toEpochMilli() - assertEquals jwt.getBody().getExpiration().getTime(), expirationMillis, 0 + assertEquals jwt.getBody().getExpiration().toEpochMilli(), expirationMillis, 0 } @Test void testParseRequireExpirationAt_Incorrect_Fail() { - def goodExpiration = new Date(System.currentTimeMillis() + 20000) - def badExpiration = new Date(System.currentTimeMillis() + 10000) + Instant goodExpiration = instantFromMillis(System.currentTimeMillis() + 20000) + + Instant badExpiration = instantFromMillis(System.currentTimeMillis() + 10000) byte[] key = randomKey() @@ -1330,7 +1330,7 @@ class JwtParserTest { @Test void testParseRequireExpiration_Missing_Fail() { - def expiration = new Date(System.currentTimeMillis() + 10000) + Instant expiration = instantFromMillis(System.currentTimeMillis() + 10000) byte[] key = randomKey() @@ -1354,7 +1354,7 @@ class JwtParserTest { @Test void testParseRequireNotBefore_Success() { // expire in the future - def notBefore = new Date(System.currentTimeMillis() - 10000) + Instant notBefore = instantFromMillis(System.currentTimeMillis() - 10000) byte[] key = randomKey() @@ -1367,15 +1367,16 @@ class JwtParserTest { parseClaimsJws(compact) // system converts to seconds (lopping off millis precision), then returns millis - def notBeforeMillis = ((long)notBefore.getTime() / 1000) * 1000 + def notBeforeMillis = notBefore.toEpochMilli() - assertEquals jwt.getBody().getNotBefore().getTime(), notBeforeMillis, 0 + assertEquals jwt.getBody().getNotBefore().toEpochMilli(), notBeforeMillis, 0 } @Test void testParseRequireNotBefore_Incorrect_Fail() { - def goodNotBefore = new Date(System.currentTimeMillis() - 20000) - def badNotBefore = new Date(System.currentTimeMillis() - 10000) + Instant goodNotBefore = instantFromMillis(System.currentTimeMillis() - 20000) + + Instant badNotBefore = instantFromMillis(System.currentTimeMillis() - 10000) byte[] key = randomKey() @@ -1398,7 +1399,7 @@ class JwtParserTest { @Test void testParseRequireNotBefore_Missing_Fail() { - def notBefore = new Date(System.currentTimeMillis() - 10000) + Instant notBefore = instantFromMillis(System.currentTimeMillis() - 10000) byte[] key = randomKey() @@ -1421,40 +1422,41 @@ class JwtParserTest { @Test void testParseRequireCustomDate_Success() { - def aDate = new Date(System.currentTimeMillis()) + Instant anInstant = instantFromMillis(System.currentTimeMillis()) byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). - claim("aDate", aDate). + claim("anInstant", anInstant). compact() Jwt jwt = Jwts.parser().setSigningKey(key). - require("aDate", aDate). + require("anInstant", anInstant). parseClaimsJws(compact) - assertEquals jwt.getBody().get("aDate", Date.class), aDate + assertEquals jwt.getBody().get("anInstant", Instant.class), anInstant } @Test void testParseRequireCustomDate_Incorrect_Fail() { - def goodDate = new Date(System.currentTimeMillis()) - def badDate = new Date(System.currentTimeMillis() - 10000) + Instant goodInstant = instantFromMillis(System.currentTimeMillis()) + + Instant badInstant = instantFromMillis(System.currentTimeMillis() - 10000) byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). - claim("aDate", badDate). + claim("anInstant", badInstant). compact() try { Jwts.parser().setSigningKey(key). - require("aDate", goodDate). + require("anInstant", goodInstant). parseClaimsJws(compact) fail() } catch(IncorrectClaimException e) { assertEquals( - String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, "aDate", goodDate, badDate), + String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, "anInstant", goodInstant, badInstant), e.getMessage() ) } @@ -1463,7 +1465,7 @@ class JwtParserTest { @Test void testParseRequireCustomDate_Missing_Fail() { - def aDate = new Date(System.currentTimeMillis()) + Instant anInstant = instantFromMillis(System.currentTimeMillis()) byte[] key = randomKey() @@ -1473,12 +1475,12 @@ class JwtParserTest { try { Jwts.parser().setSigningKey(key). - require("aDate", aDate). + require("anInstant", anInstant). parseClaimsJws(compact) fail() } catch(MissingClaimException e) { assertEquals( - String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, "aDate", aDate), + String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, "anInstant", anInstant), e.getMessage() ) } @@ -1487,8 +1489,8 @@ class JwtParserTest { @Test void testParseClockManipulationWithFixedClock() { def then = System.currentTimeMillis() - 1000 - Date expiry = new Date(then) - Date beforeExpiry = new Date(then - 1000) + Instant expiry = instantFromMillis(then) + Instant beforeExpiry = instantFromMillis(then - 1000) String compact = Jwts.builder().setSubject('Joe').setExpiration(expiry).compact() @@ -1507,7 +1509,7 @@ class JwtParserTest { @Test void testParseClockManipulationWithDefaultClock() { - Date expiry = new Date(System.currentTimeMillis() - 1000) + Instant expiry = instantFromMillis(System.currentTimeMillis() - 1000) String compact = Jwts.builder().setSubject('Joe').setExpiration(expiry).compact() @@ -1590,4 +1592,9 @@ class JwtParserTest { assertEquals 'JWT string has a digest/signature, but the header does not reference a valid signature algorithm.', se.message } } + + // The JWT RFC *mandates* time claim values are represented as seconds. + private Instant instantFromMillis(final long millis) { + return Instant.ofEpochMilli(millis).truncatedTo(ChronoUnit.SECONDS) + } } diff --git a/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy index 9a344f0b9..495b60f6e 100644 --- a/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy +++ b/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy @@ -25,7 +25,6 @@ import io.jsonwebtoken.impl.compression.GzipCompressionCodec import io.jsonwebtoken.impl.crypto.EllipticCurveProvider import io.jsonwebtoken.impl.crypto.MacProvider import io.jsonwebtoken.impl.crypto.RsaProvider -import io.jsonwebtoken.lang.Strings import org.junit.Test import javax.crypto.Mac @@ -34,6 +33,8 @@ import java.nio.charset.Charset import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey +import java.time.Instant +import java.time.temporal.ChronoUnit import static org.junit.Assert.* @@ -246,31 +247,29 @@ class JwtsTest { assertNull claims.getAudience() } - private static Date now() { - return dateWithOnlySecondPrecision(System.currentTimeMillis()); + private static Instant now() { + return instantWithOnlySecondPrecision(System.currentTimeMillis()); } private static int later() { - return laterDate().getTime() / 1000; + return laterInstant().getEpochSecond(); } - private static Date laterDate(int seconds) { - return dateWithOnlySecondPrecision(System.currentTimeMillis() + (seconds * 1000)); + private static Instant laterInstant(int seconds) { + return instantWithOnlySecondPrecision(System.currentTimeMillis() + (seconds * 1000)); } - private static Date laterDate() { - return laterDate(10000); + private static Instant laterInstant() { + return laterInstant(10000); } - private static Date dateWithOnlySecondPrecision(long millis) { - long seconds = millis / 1000; - long secondOnlyPrecisionMillis = seconds * 1000; - return new Date(secondOnlyPrecisionMillis); + private static Instant instantWithOnlySecondPrecision(long millis) { + return Instant.ofEpochMilli(millis).truncatedTo(ChronoUnit.SECONDS) } @Test void testConvenienceExpiration() { - Date then = laterDate(); + Instant then = laterInstant(); String compact = Jwts.builder().setExpiration(then).compact(); Claims claims = Jwts.parser().parse(compact).body as Claims def claimedDate = claims.getExpiration() @@ -287,11 +286,11 @@ class JwtsTest { @Test void testConvenienceNotBefore() { - Date now = now() //jwt exp only supports *seconds* since epoch: + Instant now = now() //jwt exp only supports *seconds* since epoch: String compact = Jwts.builder().setNotBefore(now).compact(); Claims claims = Jwts.parser().parse(compact).body as Claims - def claimedDate = claims.getNotBefore() - assertEquals claimedDate, now + def claimedInstant = claims.getNotBefore() + assertEquals claimedInstant, now compact = Jwts.builder().setIssuer("Me") .setNotBefore(now) //set it @@ -304,11 +303,11 @@ class JwtsTest { @Test void testConvenienceIssuedAt() { - Date now = now() //jwt exp only supports *seconds* since epoch: + Instant now = now() //jwt exp only supports *seconds* since epoch: String compact = Jwts.builder().setIssuedAt(now).compact(); Claims claims = Jwts.parser().parse(compact).body as Claims - def claimedDate = claims.getIssuedAt() - assertEquals claimedDate, now + def claimedInstant = claims.getIssuedAt() + assertEquals claimedInstant, now compact = Jwts.builder().setIssuer("Me") .setIssuedAt(now) //set it diff --git a/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy index 4d6423224..65da946f3 100644 --- a/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy +++ b/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy @@ -19,6 +19,9 @@ import io.jsonwebtoken.Claims import io.jsonwebtoken.RequiredTypeException import org.junit.Before import org.junit.Test + +import java.time.Instant + import static org.junit.Assert.* class DefaultClaimsTest { @@ -152,43 +155,43 @@ class DefaultClaimsTest { } @Test - void testGetClaimWithRequiredType_Date_Success() { - def actual = new Date(); - claims.put("aDate", actual) - Date expected = claims.get("aDate", Date.class); + void testGetClaimWithRequiredType_Instant_Success() { + def actual = Instant.now(); + claims.put("anInstant", actual) + Instant expected = claims.get("anInstant", Instant.class); assertEquals(expected, actual) } @Test - void testGetClaimWithRequiredType_DateWithLong_Success() { - def actual = new Date(); + void testGetClaimWithRequiredType_InstantWithLong_Success() { + Instant actual = Instant.ofEpochMilli(System.currentTimeMillis()) // note that Long is stored in claim - claims.put("aDate", actual.getTime()) - Date expected = claims.get("aDate", Date.class); - assertEquals(expected, actual) + claims.put("aInstant", actual.getEpochSecond()) + Instant expected = claims.get("aInstant", Instant.class); + assertEquals(expected.getEpochSecond(), actual.getEpochSecond()) } @Test void testGetClaimExpiration_Success() { - def now = new Date(System.currentTimeMillis()) + Instant now = Instant.ofEpochMilli(System.currentTimeMillis()) claims.setExpiration(now) - Date expected = claims.get("exp", Date.class) + Instant expected = claims.get("exp", Instant.class) assertEquals(expected, claims.getExpiration()) } @Test void testGetClaimIssuedAt_Success() { - def now = new Date(System.currentTimeMillis()) + Instant now = Instant.ofEpochMilli(System.currentTimeMillis()) claims.setIssuedAt(now) - Date expected = claims.get("iat", Date.class) + Instant expected = claims.get("iat", Instant.class) assertEquals(expected, claims.getIssuedAt()) } @Test void testGetClaimNotBefore_Success() { - def now = new Date(System.currentTimeMillis()) + Instant now = Instant.ofEpochMilli(System.currentTimeMillis()) claims.setNotBefore(now) - Date expected = claims.get("nbf", Date.class) + Instant expected = claims.get("nbf", Instant.class) assertEquals(expected, claims.getNotBefore()) } diff --git a/src/test/groovy/io/jsonwebtoken/impl/FixedClockTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/FixedClockTest.groovy index fc092e6a0..4e9d41d19 100644 --- a/src/test/groovy/io/jsonwebtoken/impl/FixedClockTest.groovy +++ b/src/test/groovy/io/jsonwebtoken/impl/FixedClockTest.groovy @@ -10,10 +10,10 @@ class FixedClockTest { def clock = new FixedClock() - def date1 = clock.now() + def now1 = clock.now() Thread.sleep(100) - def date2 = clock.now() + def now2 = clock.now() - assertSame date1, date2 + assertSame now1, now2 } } diff --git a/src/test/groovy/io/jsonwebtoken/impl/JwtMapTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/JwtMapTest.groovy index 446eeddd8..79efe966e 100644 --- a/src/test/groovy/io/jsonwebtoken/impl/JwtMapTest.groovy +++ b/src/test/groovy/io/jsonwebtoken/impl/JwtMapTest.groovy @@ -16,47 +16,51 @@ package io.jsonwebtoken.impl import org.junit.Test + +import java.time.Instant +import java.time.LocalDateTime +import java.time.Month +import java.time.ZoneOffset + import static org.junit.Assert.* class JwtMapTest { @Test - void testToDateFromNull() { - Date actual = JwtMap.toDate(null, 'foo') + void testToInstantFromNull() { + Instant actual = JwtMap.toInstant(null, 'foo') assertNull actual } @Test - void testToDateFromDate() { + void testToInstantFromInstant() { - def d = new Date() + def now = Instant.now() - Date date = JwtMap.toDate(d, 'foo') + Instant convertedInstant = JwtMap.toInstant(now, 'foo') - assertSame date, d + assertSame convertedInstant, now } @Test - void testToDateFromString() { - - Date d = new Date(2015, 1, 1, 12, 0, 0) - - String s = (d.getTime() / 1000) + '' //JWT timestamps are in seconds - need to strip millis + void testToInstantFromString() { - Date date = JwtMap.toDate(s, 'foo') + Instant date = LocalDateTime.of(2015, Month.JANUARY, 1, 12, 0, 0) + .toInstant(ZoneOffset.UTC) - assertEquals date, d + Instant convertedInstant = JwtMap.toInstant(String.valueOf(date.getEpochSecond()), 'foo') + assertEquals date, convertedInstant } @Test - void testToDateFromNonDateObject() { + void testToInstantFromNonInstantObject() { try { - JwtMap.toDate(new Object() { @Override public String toString() {return 'hi'} }, 'foo') + JwtMap.toInstant(new Object() { @Override public String toString() {return 'hi'} }, 'foo') fail() } catch (IllegalStateException iae) { - assertEquals iae.message, "Cannot convert 'foo' value [hi] to Date instance." + assertEquals iae.message, "Cannot convert 'foo' value [hi] to Instant instance." } }