Skip to content

Commit 90cea2d

Browse files
authored
[SDK-3149] Add Instant support (#537)
* [SDK-3149] Add support for java.time.Instant * formatting fixes * add javadocs * remove custom Instant claim verification check (not yet supported) * add tests for default method implementations * fix NullClaimTest for asInstant
1 parent ed91dc6 commit 90cea2d

20 files changed

+504
-123
lines changed

lib/src/main/java/com/auth0/jwt/JWTCreator.java

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.fasterxml.jackson.databind.module.SimpleModule;
1313

1414
import java.nio.charset.StandardCharsets;
15+
import java.time.Instant;
1516
import java.util.*;
1617
import java.util.Map.Entry;
1718

@@ -152,7 +153,19 @@ public Builder withExpiresAt(Date expiresAt) {
152153
}
153154

154155
/**
155-
* Add a specific Not Before ("nbf") claim to the Payload. The claim will be written as seconds since the epoch.
156+
* Add a specific Expires At ("exp") claim to the payload. The claim will be written as seconds since the epoch;
157+
* Milliseconds will be truncated by rounding down to the nearest second.
158+
*
159+
* @param expiresAt the Expires At value.
160+
* @return this same Builder instance.
161+
*/
162+
public Builder withExpiresAt(Instant expiresAt) {
163+
addClaim(PublicClaims.EXPIRES_AT, expiresAt);
164+
return this;
165+
}
166+
167+
/**
168+
* Add a specific Not Before ("nbf") claim to the Payload. The claim will be written as seconds since the epoch;
156169
* Milliseconds will be truncated by rounding down to the nearest second.
157170
*
158171
* @param notBefore the Not Before value.
@@ -164,7 +177,19 @@ public Builder withNotBefore(Date notBefore) {
164177
}
165178

166179
/**
167-
* Add a specific Issued At ("iat") claim to the Payload. The claim will be written as seconds since the epoch.
180+
* Add a specific Not Before ("nbf") claim to the Payload. The claim will be written as seconds since the epoch;
181+
* Milliseconds will be truncated by rounding down to the nearest second.
182+
*
183+
* @param notBefore the Not Before value.
184+
* @return this same Builder instance.
185+
*/
186+
public Builder withNotBefore(Instant notBefore) {
187+
addClaim(PublicClaims.NOT_BEFORE, notBefore);
188+
return this;
189+
}
190+
191+
/**
192+
* Add a specific Issued At ("iat") claim to the Payload. The claim will be written as seconds since the epoch;
168193
* Milliseconds will be truncated by rounding down to the nearest second.
169194
*
170195
* @param issuedAt the Issued At value.
@@ -175,6 +200,18 @@ public Builder withIssuedAt(Date issuedAt) {
175200
return this;
176201
}
177202

203+
/**
204+
* Add a specific Issued At ("iat") claim to the Payload. The claim will be written as seconds since the epoch;
205+
* Milliseconds will be truncated by rounding down to the nearest second.
206+
*
207+
* @param issuedAt the Issued At value.
208+
* @return this same Builder instance.
209+
*/
210+
public Builder withIssuedAt(Instant issuedAt) {
211+
addClaim(PublicClaims.ISSUED_AT, issuedAt);
212+
return this;
213+
}
214+
178215
/**
179216
* Add a specific JWT Id ("jti") claim to the Payload.
180217
*
@@ -271,6 +308,21 @@ public Builder withClaim(String name, Date value) throws IllegalArgumentExceptio
271308
return this;
272309
}
273310

311+
/**
312+
* Add a custom Claim value. The claim will be written as seconds since the epoch.
313+
* Milliseconds will be truncated by rounding down to the nearest second.
314+
*
315+
* @param name the Claim's name.
316+
* @param value the Claim's value.
317+
* @return this same Builder instance.
318+
* @throws IllegalArgumentException if the name is null.
319+
*/
320+
public Builder withClaim(String name, Instant value) throws IllegalArgumentException {
321+
assertNonNull(name);
322+
addClaim(name, value);
323+
return this;
324+
}
325+
274326
/**
275327
* Add a custom Array Claim with the given items.
276328
*
@@ -453,7 +505,7 @@ private static boolean isBasicType(Object value) {
453505
if (c.isArray()) {
454506
return c == Integer[].class || c == Long[].class || c == String[].class;
455507
}
456-
return c == String.class || c == Integer.class || c == Long.class || c == Double.class || c == Date.class || c == Boolean.class;
508+
return c == String.class || c == Integer.class || c == Long.class || c == Double.class || c == Date.class || c == Instant.class || c == Boolean.class;
457509
}
458510

459511
/**

lib/src/main/java/com/auth0/jwt/JWTDecoder.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import java.io.Serializable;
1111
import java.nio.charset.StandardCharsets;
12+
import java.time.Instant;
1213
import java.util.Base64;
1314
import java.util.Date;
1415
import java.util.List;
@@ -93,16 +94,31 @@ public Date getExpiresAt() {
9394
return payload.getExpiresAt();
9495
}
9596

97+
@Override
98+
public Instant getExpiresAtAsInstant() {
99+
return payload.getExpiresAtAsInstant();
100+
}
101+
96102
@Override
97103
public Date getNotBefore() {
98104
return payload.getNotBefore();
99105
}
100106

107+
@Override
108+
public Instant getNotBeforeAsInstant() {
109+
return payload.getNotBeforeAsInstant();
110+
}
111+
101112
@Override
102113
public Date getIssuedAt() {
103114
return payload.getIssuedAt();
104115
}
105116

117+
@Override
118+
public Instant getIssuedAtAsInstant() {
119+
return payload.getIssuedAtAsInstant();
120+
}
121+
106122
@Override
107123
public String getId() {
108124
return payload.getId();

lib/src/main/java/com/auth0/jwt/JWTVerifier.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99
import com.auth0.jwt.interfaces.DecodedJWT;
1010
import com.auth0.jwt.interfaces.Verification;
1111

12-
import java.util.*;
1312
import java.time.Clock;
13+
import java.time.Duration;
14+
import java.time.Instant;
15+
import java.time.temporal.ChronoUnit;
16+
import java.util.*;
1417

1518
/**
1619
* The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format, but also its signature matches.
@@ -327,13 +330,13 @@ private void verifyClaimValues(DecodedJWT jwt, Map.Entry<String, Object> expecte
327330
assertValidAudienceClaim(jwt.getAudience(), (List<String>) expectedClaim.getValue(), false);
328331
break;
329332
case PublicClaims.EXPIRES_AT:
330-
assertValidDateClaim(jwt.getExpiresAt(), (Long) expectedClaim.getValue(), true);
333+
assertValidInstantClaim(jwt.getExpiresAtAsInstant(), (Long) expectedClaim.getValue(), true);
331334
break;
332335
case PublicClaims.ISSUED_AT:
333-
assertValidDateClaim(jwt.getIssuedAt(), (Long) expectedClaim.getValue(), false);
336+
assertValidInstantClaim(jwt.getIssuedAtAsInstant(), (Long) expectedClaim.getValue(), false);
334337
break;
335338
case PublicClaims.NOT_BEFORE:
336-
assertValidDateClaim(jwt.getNotBefore(), (Long) expectedClaim.getValue(), false);
339+
assertValidInstantClaim(jwt.getNotBeforeAsInstant(), (Long) expectedClaim.getValue(), false);
337340
break;
338341
case PublicClaims.ISSUER:
339342
assertValidIssuerClaim(jwt.getIssuer(), (List<String>) expectedClaim.getValue());
@@ -403,27 +406,24 @@ private void assertValidStringClaim(String claimName, String value, String expec
403406
}
404407
}
405408

406-
private void assertValidDateClaim(Date date, long leeway, boolean shouldBeFuture) {
407-
Date today = new Date(clock.millis());
408-
today.setTime(today.getTime() / 1000 * 1000); // truncate millis
409+
private void assertValidInstantClaim(Instant claimVal, long leeway, boolean shouldBeFuture) {
410+
Instant now = clock.instant().truncatedTo(ChronoUnit.SECONDS);
409411
if (shouldBeFuture) {
410-
assertDateIsFuture(date, leeway, today);
412+
assertInstantIsFuture(claimVal, leeway, now);
411413
} else {
412-
assertDateIsPast(date, leeway, today);
414+
assertInstantIsPast(claimVal, leeway, now);
413415
}
414416
}
415417

416-
private void assertDateIsFuture(Date date, long leeway, Date today) {
417-
today.setTime(today.getTime() - leeway * 1000);
418-
if (date != null && today.after(date)) {
419-
throw new TokenExpiredException(String.format("The Token has expired on %s.", date));
418+
private void assertInstantIsFuture(Instant claimVal, long leeway, Instant now) {
419+
if (claimVal != null && now.minus(Duration.ofSeconds(leeway)).isAfter(claimVal)) {
420+
throw new TokenExpiredException(String.format("The Token has expired on %s.", claimVal));
420421
}
421422
}
422423

423-
private void assertDateIsPast(Date date, long leeway, Date today) {
424-
today.setTime(today.getTime() + leeway * 1000);
425-
if (date != null && today.before(date)) {
426-
throw new InvalidClaimException(String.format("The Token can't be used before %s.", date));
424+
private void assertInstantIsPast(Instant claimVal, long leeway, Instant now) {
425+
if (claimVal != null && now.plus(Duration.ofSeconds(leeway)).isBefore(claimVal)) {
426+
throw new InvalidClaimException(String.format("The Token can't be used before %s.", claimVal));
427427
}
428428
}
429429

lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
import com.fasterxml.jackson.core.JsonProcessingException;
77
import com.fasterxml.jackson.core.type.TypeReference;
88
import com.fasterxml.jackson.databind.JsonNode;
9-
import com.fasterxml.jackson.databind.ObjectMapper;
109
import com.fasterxml.jackson.databind.ObjectReader;
1110

1211
import java.io.IOException;
1312
import java.lang.reflect.Array;
13+
import java.time.Instant;
1414
import java.util.ArrayList;
1515
import java.util.Date;
1616
import java.util.List;
@@ -63,6 +63,15 @@ public Date asDate() {
6363
return new Date(seconds * 1000);
6464
}
6565

66+
@Override
67+
public Instant asInstant() {
68+
if (!data.canConvertToLong()) {
69+
return null;
70+
}
71+
long seconds = data.asLong();
72+
return Instant.ofEpochSecond(seconds);
73+
}
74+
6675
@Override
6776
@SuppressWarnings("unchecked")
6877
public <T> T[] asArray(Class<T> tClazz) throws JWTDecodeException {

lib/src/main/java/com/auth0/jwt/impl/NullClaim.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.auth0.jwt.exceptions.JWTDecodeException;
44
import com.auth0.jwt.interfaces.Claim;
55

6+
import java.time.Instant;
67
import java.util.Date;
78
import java.util.List;
89
import java.util.Map;
@@ -46,6 +47,11 @@ public Date asDate() {
4647
return null;
4748
}
4849

50+
@Override
51+
public Instant asInstant() {
52+
return null;
53+
}
54+
4955
@Override
5056
public <T> T[] asArray(Class<T> tClazz) throws JWTDecodeException {
5157
return null;

lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
1212

1313
import java.io.IOException;
14+
import java.time.Instant;
1415
import java.util.*;
1516

1617
/**
@@ -45,9 +46,9 @@ public Payload deserialize(JsonParser p, DeserializationContext ctxt) throws IOE
4546
String issuer = getString(tree, PublicClaims.ISSUER);
4647
String subject = getString(tree, PublicClaims.SUBJECT);
4748
List<String> audience = getStringOrArray(tree, PublicClaims.AUDIENCE);
48-
Date expiresAt = getDateFromSeconds(tree, PublicClaims.EXPIRES_AT);
49-
Date notBefore = getDateFromSeconds(tree, PublicClaims.NOT_BEFORE);
50-
Date issuedAt = getDateFromSeconds(tree, PublicClaims.ISSUED_AT);
49+
Instant expiresAt = getInstantFromSeconds(tree, PublicClaims.EXPIRES_AT);
50+
Instant notBefore = getInstantFromSeconds(tree, PublicClaims.NOT_BEFORE);
51+
Instant issuedAt = getInstantFromSeconds(tree, PublicClaims.ISSUED_AT);
5152
String jwtId = getString(tree, PublicClaims.JWT_ID);
5253

5354
return new PayloadImpl(issuer, subject, audience, expiresAt, notBefore, issuedAt, jwtId, tree, objectReader);
@@ -73,16 +74,15 @@ List<String> getStringOrArray(Map<String, JsonNode> tree, String claimName) thro
7374
return list;
7475
}
7576

76-
Date getDateFromSeconds(Map<String, JsonNode> tree, String claimName) {
77+
Instant getInstantFromSeconds(Map<String, JsonNode> tree, String claimName) {
7778
JsonNode node = tree.get(claimName);
7879
if (node == null || node.isNull()) {
7980
return null;
8081
}
8182
if (!node.canConvertToLong()) {
8283
throw new JWTDecodeException(String.format("The claim '%s' contained a non-numeric date value.", claimName));
8384
}
84-
final long ms = node.asLong() * 1000;
85-
return new Date(ms);
85+
return Instant.ofEpochSecond(node.asLong());
8686
}
8787

8888
String getString(Map<String, JsonNode> tree, String claimName) {

lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.fasterxml.jackson.databind.ObjectReader;
77

88
import java.io.Serializable;
9+
import java.time.Instant;
910
import java.util.*;
1011

1112
import static com.auth0.jwt.impl.JsonNodeClaim.extractClaim;
@@ -24,14 +25,14 @@ class PayloadImpl implements Payload, Serializable {
2425
private final String issuer;
2526
private final String subject;
2627
private final List<String> audience;
27-
private final Date expiresAt;
28-
private final Date notBefore;
29-
private final Date issuedAt;
28+
private final Instant expiresAt;
29+
private final Instant notBefore;
30+
private final Instant issuedAt;
3031
private final String jwtId;
3132
private final Map<String, JsonNode> tree;
3233
private final ObjectReader objectReader;
3334

34-
PayloadImpl(String issuer, String subject, List<String> audience, Date expiresAt, Date notBefore, Date issuedAt, String jwtId, Map<String, JsonNode> tree, ObjectReader objectReader) {
35+
PayloadImpl(String issuer, String subject, List<String> audience, Instant expiresAt, Instant notBefore, Instant issuedAt, String jwtId, Map<String, JsonNode> tree, ObjectReader objectReader) {
3536
this.issuer = issuer;
3637
this.subject = subject;
3738
this.audience = audience != null ? Collections.unmodifiableList(audience) : null;
@@ -64,19 +65,35 @@ public List<String> getAudience() {
6465

6566
@Override
6667
public Date getExpiresAt() {
67-
return expiresAt;
68+
return (expiresAt != null) ? Date.from(expiresAt) : null;
6869
}
6970

71+
7072
@Override
71-
public Date getNotBefore() {
72-
return notBefore;
73+
public Instant getExpiresAtAsInstant() {
74+
return expiresAt;
7375
}
7476

7577
@Override
7678
public Date getIssuedAt() {
79+
return (issuedAt != null) ? Date.from(issuedAt) : null;
80+
}
81+
82+
@Override
83+
public Instant getIssuedAtAsInstant() {
7784
return issuedAt;
7885
}
7986

87+
@Override
88+
public Date getNotBefore() {
89+
return (notBefore != null) ? Date.from(notBefore) : null;
90+
}
91+
92+
@Override
93+
public Instant getNotBeforeAsInstant() {
94+
return notBefore;
95+
}
96+
8097
@Override
8198
public String getId() {
8299
return jwtId;

lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
66

77
import java.io.IOException;
8+
import java.time.Instant;
89
import java.util.*;
910

1011
/**
@@ -71,13 +72,15 @@ private void writeAudience(JsonGenerator gen, Map.Entry<String, Object> e) throw
7172
}
7273

7374
/**
74-
* Serializes {@linkplain Date} to epoch second values, traversing maps and lists as needed.
75+
* Serializes {@linkplain Instant} to epoch second values, traversing maps and lists as needed.
7576
* @param value the object to serialize
7677
* @param gen the JsonGenerator to use for JSON serialization
7778
*/
7879
private void handleSerialization(Object value, JsonGenerator gen) throws IOException {
79-
if (value instanceof Date) { // EXPIRES_AT, ISSUED_AT, NOT_BEFORE, custom date claims
80+
if (value instanceof Date) {
8081
gen.writeNumber(dateToSeconds((Date) value));
82+
} else if (value instanceof Instant) { // EXPIRES_AT, ISSUED_AT, NOT_BEFORE, custom Instant claims
83+
gen.writeNumber(instantToSeconds((Instant) value));
8184
} else if (value instanceof Map) {
8285
serializeMap((Map<?, ?>) value, gen);
8386
} else if (value instanceof List) {
@@ -105,6 +108,10 @@ private void serializeList(List<?> list, JsonGenerator gen) throws IOException {
105108
gen.writeEndArray();
106109
}
107110

111+
private long instantToSeconds(Instant instant) {
112+
return instant.getEpochSecond();
113+
}
114+
108115
private long dateToSeconds(Date date) {
109116
return date.getTime() / 1000;
110117
}

0 commit comments

Comments
 (0)