diff --git a/CHANGELOG.md b/CHANGELOG.md index eaf3feec..61ebad87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log +## [4.3.0](https://github.com/auth0/java-jwt/tree/4.3.0) (2023-02-10) +[Full Changelog](https://github.com/auth0/java-jwt/compare/4.2.2...4.3.0) + +**Changed** +- Improve JWT parse/decode performance [\#620](https://github.com/auth0/java-jwt/pull/620) ([noetro](https://github.com/noetro)) + +**Fixed** +- Fix for exp claim considered valid if equal to now [\#652](https://github.com/auth0/java-jwt/pull/652) ([jimmyjames](https://github.com/jimmyjames)) +- Code cleanup [\#642](https://github.com/auth0/java-jwt/pull/642) ([CodeDead](https://github.com/CodeDead)) + ## [4.2.2](https://github.com/auth0/java-jwt/tree/4.2.2) (2023-01-11) [Full Changelog](https://github.com/auth0/java-jwt/compare/4.2.1...4.2.2) diff --git a/EXAMPLES.md b/EXAMPLES.md index 8a849303..995e4c1d 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -127,4 +127,4 @@ RSAKeyProvider keyProvider = new RSAKeyProvider() { Algorithm algorithm = Algorithm.RSA256(keyProvider); //Use the Algorithm to create and verify JWTs. -``` \ No newline at end of file +``` diff --git a/LICENSE b/LICENSE index 4a7a13ad..bcd1854c 100644 --- a/LICENSE +++ b/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index 8583ed7a..9391f87d 100644 --- a/README.md +++ b/README.md @@ -50,14 +50,14 @@ Add the dependency via Maven: com.auth0 java-jwt - 4.2.2 + 4.3.0 ``` or Gradle: ```gradle -implementation 'com.auth0:java-jwt:4.2.2' +implementation 'com.auth0:java-jwt:4.3.0' ``` ### Create a JWT diff --git a/lib/build.gradle b/lib/build.gradle index 6b2fdfe3..6190a239 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -6,10 +6,28 @@ plugins { id 'checkstyle' } +sourceSets { + jmh { + + } +} + +configurations { + jmhImplementation { + extendsFrom implementation + } +} + checkstyle { toolVersion '10.0' - checkstyleTest.enabled = false //We are disabling lint checks for tests } +//We are disabling lint checks for tests +tasks.named("checkstyleTest").configure({ + enabled = false +}) +tasks.named("checkstyleJmh").configure({ + enabled = false +}) logger.lifecycle("Using version ${version} for ${group}.${name}") @@ -61,6 +79,10 @@ dependencies { testImplementation 'net.jodah:concurrentunit:0.4.6' testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation 'org.mockito:mockito-core:4.4.0' + + jmhImplementation sourceSets.main.output + jmhImplementation 'org.openjdk.jmh:jmh-core:1.35' + jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.35' } jacoco { @@ -143,3 +165,25 @@ task exportVersion() { new File(rootDir, "version.txt").text = "$version" } } + +// you can pass any arguments JMH accepts via Gradle args. +// Example: ./gradlew runJMH --args="-lrf" +tasks.register('runJMH', JavaExec) { + description 'Run JMH benchmarks.' + group 'verification' + + main 'org.openjdk.jmh.Main' + classpath sourceSets.jmh.runtimeClasspath + + args project.hasProperty("args") ? project.property("args").split() : "" +} +tasks.register('jmhHelp', JavaExec) { + description 'Prints the available command line options for JMH.' + group 'help' + + main 'org.openjdk.jmh.Main' + classpath sourceSets.jmh.runtimeClasspath + + args '-h' +} + diff --git a/lib/src/jmh/java/com/auth0/jwt/benchmark/JWTDecoderBenchmark.java b/lib/src/jmh/java/com/auth0/jwt/benchmark/JWTDecoderBenchmark.java new file mode 100644 index 00000000..81d3737a --- /dev/null +++ b/lib/src/jmh/java/com/auth0/jwt/benchmark/JWTDecoderBenchmark.java @@ -0,0 +1,20 @@ +package com.auth0.jwt.benchmark; + +import com.auth0.jwt.JWT; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.infra.Blackhole; + +/** + * This class is a JMH benchmark for decoding JWTs. + */ +public class JWTDecoderBenchmark { + private static final String TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"; + + @Benchmark + @BenchmarkMode(Mode.Throughput) + public void throughputDecodeTime(Blackhole blackhole) { + blackhole.consume(JWT.decode(TOKEN)); + } +} diff --git a/lib/src/main/java/com/auth0/jwt/JWTCreator.java b/lib/src/main/java/com/auth0/jwt/JWTCreator.java index a99f0fa0..7ed83940 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTCreator.java +++ b/lib/src/main/java/com/auth0/jwt/JWTCreator.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import java.nio.charset.StandardCharsets; @@ -31,12 +32,14 @@ public final class JWTCreator { private static final SimpleModule module; static { - mapper = new ObjectMapper(); module = new SimpleModule(); module.addSerializer(PayloadClaimsHolder.class, new PayloadSerializer()); module.addSerializer(HeaderClaimsHolder.class, new HeaderSerializer()); - mapper.registerModule(module); - mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); + + mapper = JsonMapper.builder() + .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) + .build() + .registerModule(module); } private JWTCreator(Algorithm algorithm, Map headerClaims, Map payloadClaims) @@ -489,7 +492,7 @@ private static boolean validateClaim(Map map) { return false; } - if (entry.getKey() == null || !(entry.getKey() instanceof String)) { + if (!(entry.getKey() instanceof String)) { return false; } } diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 07c86a4c..6cec2026 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -346,7 +346,7 @@ private boolean assertValidInstantClaim(String claimName, Claim claim, long leew throw new TokenExpiredException(String.format("The Token has expired on %s.", claimVal), claimVal); } } else { - isValid = assertInstantIsPast(claimVal, leeway, now); + isValid = assertInstantIsLessThanOrEqualToNow(claimVal, leeway, now); if (!isValid) { throw new IncorrectClaimException( String.format("The Token can't be used before %s.", claimVal), claimName, claim); @@ -356,10 +356,10 @@ private boolean assertValidInstantClaim(String claimName, Claim claim, long leew } private boolean assertInstantIsFuture(Instant claimVal, long leeway, Instant now) { - return !(claimVal != null && now.minus(Duration.ofSeconds(leeway)).isAfter(claimVal)); + return claimVal == null || now.minus(Duration.ofSeconds(leeway)).isBefore(claimVal); } - private boolean assertInstantIsPast(Instant claimVal, long leeway, Instant now) { + private boolean assertInstantIsLessThanOrEqualToNow(Instant claimVal, long leeway, Instant now) { return !(claimVal != null && now.plus(Duration.ofSeconds(leeway)).isBefore(claimVal)); } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java index 0c7a5b57..ca892e60 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java @@ -5,7 +5,6 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.RSAKeyProvider; -import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; diff --git a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java index 3746dcd2..5a881ab5 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java +++ b/lib/src/main/java/com/auth0/jwt/impl/BasicHeader.java @@ -2,12 +2,11 @@ import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.Header; +import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectReader; import java.io.Serializable; import java.util.Collections; -import java.util.HashMap; import java.util.Map; import static com.auth0.jwt.impl.JsonNodeClaim.extractClaim; @@ -23,7 +22,7 @@ class BasicHeader implements Header, Serializable { private final String contentType; private final String keyId; private final Map tree; - private final ObjectReader objectReader; + private final ObjectCodec objectCodec; BasicHeader( String algorithm, @@ -31,14 +30,14 @@ class BasicHeader implements Header, Serializable { String contentType, String keyId, Map tree, - ObjectReader objectReader + ObjectCodec objectCodec ) { this.algorithm = algorithm; this.type = type; this.contentType = contentType; this.keyId = keyId; - this.tree = Collections.unmodifiableMap(tree == null ? new HashMap() : tree); - this.objectReader = objectReader; + this.tree = tree == null ? Collections.emptyMap() : Collections.unmodifiableMap(tree); + this.objectCodec = objectCodec; } Map getTree() { @@ -67,6 +66,6 @@ public String getKeyId() { @Override public Claim getHeaderClaim(String name) { - return extractClaim(name, tree, objectReader); + return extractClaim(name, tree, objectCodec); } } diff --git a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java index 9293fd4d..ad6e4ce0 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/HeaderDeserializer.java @@ -2,11 +2,11 @@ import com.auth0.jwt.HeaderParams; import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.interfaces.Header; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import java.io.IOException; @@ -19,22 +19,14 @@ * * @see JWTParser */ -class HeaderDeserializer extends StdDeserializer { +class HeaderDeserializer extends StdDeserializer
{ - private final ObjectReader objectReader; - - HeaderDeserializer(ObjectReader objectReader) { - this(null, objectReader); - } - - private HeaderDeserializer(Class vc, ObjectReader objectReader) { - super(vc); - - this.objectReader = objectReader; + HeaderDeserializer() { + super(Header.class); } @Override - public BasicHeader deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public Header deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { Map tree = p.getCodec().readValue(p, new TypeReference>() { }); if (tree == null) { @@ -45,7 +37,7 @@ public BasicHeader deserialize(JsonParser p, DeserializationContext ctxt) throws String type = getString(tree, HeaderParams.TYPE); String contentType = getString(tree, HeaderParams.CONTENT_TYPE); String keyId = getString(tree, HeaderParams.KEY_ID); - return new BasicHeader(algorithm, type, contentType, keyId, tree, objectReader); + return new BasicHeader(algorithm, type, contentType, keyId, tree, p.getCodec()); } String getString(Map tree, String claimName) { diff --git a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java index fe1600bd..022520f5 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JWTParser.java @@ -16,15 +16,21 @@ * {@link HeaderSerializer} and {@link PayloadSerializer}. */ public class JWTParser implements JWTPartsParser { + private static final ObjectMapper DEFAULT_OBJECT_MAPPER = createDefaultObjectMapper(); + private static final ObjectReader DEFAULT_PAYLOAD_READER = DEFAULT_OBJECT_MAPPER.readerFor(Payload.class); + private static final ObjectReader DEFAULT_HEADER_READER = DEFAULT_OBJECT_MAPPER.readerFor(Header.class); + private final ObjectReader payloadReader; private final ObjectReader headerReader; public JWTParser() { - this(getDefaultObjectMapper()); + this.payloadReader = DEFAULT_PAYLOAD_READER; + this.headerReader = DEFAULT_HEADER_READER; } JWTParser(ObjectMapper mapper) { addDeserializers(mapper); + this.payloadReader = mapper.readerFor(Payload.class); this.headerReader = mapper.readerFor(Header.class); } @@ -55,18 +61,24 @@ public Header parseHeader(String json) throws JWTDecodeException { } } - private void addDeserializers(ObjectMapper mapper) { + static void addDeserializers(ObjectMapper mapper) { SimpleModule module = new SimpleModule(); - ObjectReader reader = mapper.reader(); - module.addDeserializer(Payload.class, new PayloadDeserializer(reader)); - module.addDeserializer(Header.class, new HeaderDeserializer(reader)); + module.addDeserializer(Payload.class, new PayloadDeserializer()); + module.addDeserializer(Header.class, new HeaderDeserializer()); mapper.registerModule(module); } static ObjectMapper getDefaultObjectMapper() { + return DEFAULT_OBJECT_MAPPER; + } + + private static ObjectMapper createDefaultObjectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + + addDeserializers(mapper); + return mapper; } diff --git a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java index 456d1515..0a7e22f3 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -4,9 +4,9 @@ import com.auth0.jwt.interfaces.Claim; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectReader; import java.io.IOException; import java.lang.reflect.Array; @@ -21,12 +21,12 @@ */ class JsonNodeClaim implements Claim { - private final ObjectReader objectReader; + private final ObjectCodec codec; private final JsonNode data; - private JsonNodeClaim(JsonNode node, ObjectReader objectReader) { + private JsonNodeClaim(JsonNode node, ObjectCodec codec) { this.data = node; - this.objectReader = objectReader; + this.codec = codec; } @Override @@ -82,7 +82,7 @@ public T[] asArray(Class clazz) throws JWTDecodeException { T[] arr = (T[]) Array.newInstance(clazz, data.size()); for (int i = 0; i < data.size(); i++) { try { - arr[i] = objectReader.treeToValue(data.get(i), clazz); + arr[i] = codec.treeToValue(data.get(i), clazz); } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim's array contents to " + clazz.getSimpleName(), e); } @@ -99,7 +99,7 @@ public List asList(Class clazz) throws JWTDecodeException { List list = new ArrayList<>(); for (int i = 0; i < data.size(); i++) { try { - list.add(objectReader.treeToValue(data.get(i), clazz)); + list.add(codec.treeToValue(data.get(i), clazz)); } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim's array contents to " + clazz.getSimpleName(), e); } @@ -113,11 +113,11 @@ public Map asMap() throws JWTDecodeException { return null; } - try { - TypeReference> mapType = new TypeReference>() { - }; - JsonParser thisParser = objectReader.treeAsTokens(data); - return thisParser.readValueAs(mapType); + TypeReference> mapType = new TypeReference>() { + }; + + try (JsonParser parser = codec.treeAsTokens(data)) { + return parser.readValueAs(mapType); } catch (IOException e) { throw new JWTDecodeException("Couldn't map the Claim value to Map", e); } @@ -129,8 +129,8 @@ public T as(Class clazz) throws JWTDecodeException { if (isMissing() || isNull()) { return null; } - return objectReader.treeAsTokens(data).readValueAs(clazz); - } catch (IOException e) { + return codec.treeToValue(data, clazz); + } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim value to " + clazz.getSimpleName(), e); } } @@ -160,21 +160,23 @@ public String toString() { * * @param claimName the Claim to search for. * @param tree the JsonNode tree to search the Claim in. + * @param objectCodec the object codec in use for deserialization * @return a valid non-null Claim. */ - static Claim extractClaim(String claimName, Map tree, ObjectReader objectReader) { + static Claim extractClaim(String claimName, Map tree, ObjectCodec objectCodec) { JsonNode node = tree.get(claimName); - return claimFromNode(node, objectReader); + return claimFromNode(node, objectCodec); } /** * Helper method to create a Claim representation from the given JsonNode. * * @param node the JsonNode to convert into a Claim. + * @param objectCodec the object codec in use for deserialization * @return a valid Claim instance. If the node is null or missing, a NullClaim will be returned. */ - static Claim claimFromNode(JsonNode node, ObjectReader objectReader) { - return new JsonNodeClaim(node, objectReader); + static Claim claimFromNode(JsonNode node, ObjectCodec objectCodec) { + return new JsonNodeClaim(node, objectCodec); } } \ No newline at end of file diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index 37e70f7a..65fba3ac 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -5,6 +5,7 @@ import com.auth0.jwt.interfaces.Payload; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; @@ -24,16 +25,8 @@ */ class PayloadDeserializer extends StdDeserializer { - private final ObjectReader objectReader; - - PayloadDeserializer(ObjectReader reader) { - this(null, reader); - } - - private PayloadDeserializer(Class vc, ObjectReader reader) { - super(vc); - - this.objectReader = reader; + PayloadDeserializer() { + super(Payload.class); } @Override @@ -46,16 +39,17 @@ public Payload deserialize(JsonParser p, DeserializationContext ctxt) throws IOE String issuer = getString(tree, RegisteredClaims.ISSUER); String subject = getString(tree, RegisteredClaims.SUBJECT); - List audience = getStringOrArray(tree, RegisteredClaims.AUDIENCE); + List audience = getStringOrArray(p.getCodec(), tree, RegisteredClaims.AUDIENCE); Instant expiresAt = getInstantFromSeconds(tree, RegisteredClaims.EXPIRES_AT); Instant notBefore = getInstantFromSeconds(tree, RegisteredClaims.NOT_BEFORE); Instant issuedAt = getInstantFromSeconds(tree, RegisteredClaims.ISSUED_AT); String jwtId = getString(tree, RegisteredClaims.JWT_ID); - return new PayloadImpl(issuer, subject, audience, expiresAt, notBefore, issuedAt, jwtId, tree, objectReader); + return new PayloadImpl(issuer, subject, audience, expiresAt, notBefore, issuedAt, jwtId, tree, p.getCodec()); } - List getStringOrArray(Map tree, String claimName) throws JWTDecodeException { + List getStringOrArray(ObjectCodec codec, Map tree, String claimName) + throws JWTDecodeException { JsonNode node = tree.get(claimName); if (node == null || node.isNull() || !(node.isArray() || node.isTextual())) { return null; @@ -67,7 +61,7 @@ List getStringOrArray(Map tree, String claimName) thro List list = new ArrayList<>(node.size()); for (int i = 0; i < node.size(); i++) { try { - list.add(objectReader.treeToValue(node.get(i), String.class)); + list.add(codec.treeToValue(node.get(i), String.class)); } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim's array contents to String", e); } diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java index 75e79474..bfd9b0ea 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadImpl.java @@ -2,12 +2,16 @@ import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.Payload; +import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectReader; import java.io.Serializable; import java.time.Instant; -import java.util.*; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static com.auth0.jwt.impl.JsonNodeClaim.extractClaim; @@ -30,7 +34,7 @@ class PayloadImpl implements Payload, Serializable { private final Instant issuedAt; private final String jwtId; private final Map tree; - private final ObjectReader objectReader; + private final ObjectCodec objectCodec; PayloadImpl( String issuer, @@ -41,7 +45,7 @@ class PayloadImpl implements Payload, Serializable { Instant issuedAt, String jwtId, Map tree, - ObjectReader objectReader + ObjectCodec objectCodec ) { this.issuer = issuer; this.subject = subject; @@ -50,8 +54,8 @@ class PayloadImpl implements Payload, Serializable { this.notBefore = notBefore; this.issuedAt = issuedAt; this.jwtId = jwtId; - this.tree = tree != null ? Collections.unmodifiableMap(tree) : Collections.emptyMap(); - this.objectReader = objectReader; + this.tree = tree != null ? Collections.unmodifiableMap(tree) : Collections.emptyMap(); + this.objectCodec = objectCodec; } Map getTree() { @@ -111,14 +115,14 @@ public String getId() { @Override public Claim getClaim(String name) { - return extractClaim(name, tree, objectReader); + return extractClaim(name, tree, objectCodec); } @Override public Map getClaims() { Map claims = new HashMap<>(tree.size() * 2); for (String name : tree.keySet()) { - claims.put(name, extractClaim(name, tree, objectReader)); + claims.put(name, extractClaim(name, tree, objectCodec)); } return Collections.unmodifiableMap(claims); } diff --git a/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java b/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java index a06df7b7..32ede1de 100644 --- a/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java +++ b/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java @@ -10,14 +10,11 @@ import org.junit.rules.ExpectedException; import java.security.interfaces.ECKey; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAKey; import java.util.Collections; import java.util.List; import java.util.concurrent.*; -import static com.auth0.jwt.PemUtils.readPrivateKeyFromFile; import static com.auth0.jwt.PemUtils.readPublicKeyFromFile; //@Ignore("Skipping concurrency tests") diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 020e5e37..8fc83b44 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -16,9 +16,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/lib/src/test/java/com/auth0/jwt/JWTTest.java b/lib/src/test/java/com/auth0/jwt/JWTTest.java index b9f56a2e..087f1e9e 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTTest.java @@ -12,6 +12,7 @@ import java.security.interfaces.ECKey; import java.security.interfaces.RSAKey; import java.time.Clock; +import java.time.Duration; import java.time.Instant; import java.time.ZoneId; import java.util.Base64; @@ -270,12 +271,12 @@ public void shouldGetStringAudience() { @Test public void shouldGetExpirationTime() { long seconds = 1477592L; - Clock clock = Clock.fixed(Instant.ofEpochSecond(seconds), ZoneId.of("UTC")); + Clock mockNow = Clock.fixed(Instant.ofEpochSecond(seconds - 1), ZoneId.of("UTC")); String token = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0Nzc1OTJ9.x_ZjkPkKYUV5tdvc0l8go6D_z2kez1MQcOxokXrDc3k"; JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWT.require(Algorithm.HMAC256("secret")); DecodedJWT jwt = verification - .build(clock) + .build(mockNow) .verify(token); assertThat(jwt, is(notNullValue())); diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 6d8ba201..5a784b87 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -657,6 +657,7 @@ public void shouldThrowOnNegativeCustomLeeway() { } // Expires At + @Test public void shouldValidateExpiresAtWithLeeway() { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; @@ -674,12 +675,26 @@ public void shouldValidateExpiresAtIfPresent() { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); DecodedJWT jwt = verification - .build(mockNow) + .build(mockOneSecondEarlier) .verify(token); assertThat(jwt, is(notNullValue())); } + @Test + public void shouldThrowWhenExpiresAtIsNow() { + // exp must be > now + TokenExpiredException e = assertThrows(null, TokenExpiredException.class, () -> { + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + verification + .build(mockNow) + .verify(token); + }); + assertThat(e.getMessage(), is("The Token has expired on 1970-01-18T02:26:32Z.")); + assertThat(e.getExpiredOn(), is(Instant.ofEpochSecond(1477592L))); + } + @Test public void shouldThrowOnInvalidExpiresAtIfPresent() { TokenExpiredException e = assertThrows(null, TokenExpiredException.class, () -> { @@ -731,7 +746,18 @@ public void shouldThrowOnInvalidNotBeforeIfPresent() { @Test public void shouldValidateNotBeforeIfPresent() { - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo"; + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE0Nzc1OTN9.f4zVV0TbbTG5xxDjSoGZ320JIMchGoQCWrnT5MyQdT0"; + JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); + DecodedJWT jwt = verification + .build(mockOneSecondLater) + .verify(token); + + assertThat(jwt, is(notNullValue())); + } + + @Test + public void shouldAcceptNotBeforeEqualToNow() { + String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE0Nzc1OTJ9.71XBtRmkAa4iKnyhbS4NPW-Xr26eAVAdHZgmupS7a5o"; JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret")); DecodedJWT jwt = verification .build(mockNow) diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java index 4a0269cc..9b6ac0c0 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/HMACAlgorithmTest.java @@ -13,7 +13,6 @@ import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; import static com.auth0.jwt.algorithms.CryptoTestHelper.asJWT; import static com.auth0.jwt.algorithms.CryptoTestHelper.assertSignaturePresent; diff --git a/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java index 328f4ab4..02d782a7 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/HeaderDeserializerTest.java @@ -10,7 +10,6 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.TextNode; import org.junit.Before; @@ -22,8 +21,10 @@ import java.util.HashMap; import java.util.Map; -import static org.hamcrest.Matchers.*; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -34,11 +35,10 @@ public class HeaderDeserializerTest { @Rule public ExpectedException exception = ExpectedException.none(); private HeaderDeserializer deserializer; - private ObjectReader objectReader = new ObjectMapper().reader(); @Before public void setUp() { - deserializer = new HeaderDeserializer(objectReader); + deserializer = new HeaderDeserializer(); } @Test @@ -46,7 +46,7 @@ public void shouldThrowOnNullTree() throws Exception { exception.expect(JWTDecodeException.class); exception.expectMessage("Parsing the Header's JSON resulted on a Null map"); - JsonDeserializer deserializer = new HeaderDeserializer(objectReader); + JsonDeserializer deserializer = new HeaderDeserializer(); JsonParser parser = mock(JsonParser.class); ObjectCodec codec = mock(ObjectCodec.class); DeserializationContext context = mock(DeserializationContext.class); diff --git a/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java b/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java index a40a23d4..da62131a 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JWTParserTest.java @@ -42,7 +42,7 @@ public void shouldGetDefaultObjectMapper() { @Test public void shouldAddDeserializers() { ObjectMapper mapper = mock(ObjectMapper.class); - new JWTParser(mapper); + JWTParser.addDeserializers(mapper); verify(mapper).registerModule(any(Module.class)); } diff --git a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java index 18f59f04..a0364953 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.MissingNode; import com.fasterxml.jackson.databind.node.NullNode; @@ -21,20 +20,31 @@ import java.io.IOException; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; import static com.auth0.jwt.impl.JWTParser.getDefaultObjectMapper; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; import static org.hamcrest.core.IsNull.notNullValue; import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; public class JsonNodeClaimTest { private ObjectMapper mapper; - private ObjectReader objectReader; @Rule public ExpectedException exception = ExpectedException.none(); @@ -42,7 +52,6 @@ public class JsonNodeClaimTest { @Before public void setUp() { mapper = getDefaultObjectMapper(); - objectReader = mapper.reader(); } @Test @@ -55,7 +64,7 @@ public void shouldGetBooleanValue() { } private Claim claimFromNode(JsonNode value) { - return JsonNodeClaim.claimFromNode(value, objectReader); + return JsonNodeClaim.claimFromNode(value, mapper); } @Test @@ -282,7 +291,7 @@ public void shouldThrowIfAnExtraordinaryExceptionHappensWhenParsingAsGenericMap( JsonNode value = mock(ObjectNode.class); when(value.getNodeType()).thenReturn(JsonNodeType.OBJECT); - ObjectReader mockedMapper = mock(ObjectReader.class); + ObjectMapper mockedMapper = mock(ObjectMapper.class); JsonNodeClaim claim = (JsonNodeClaim) JsonNodeClaim.claimFromNode(value, mockedMapper); JsonNodeClaim spiedClaim = spy(claim); diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java index 2d80bed9..86c4f10f 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java @@ -35,9 +35,12 @@ public class PayloadDeserializerTest { public ExpectedException exception = ExpectedException.none(); private PayloadDeserializer deserializer; + private ObjectMapper objectMapper; + @Before public void setUp() { - deserializer = new PayloadDeserializer(new ObjectMapper().reader()); + objectMapper = new ObjectMapper(); + deserializer = new PayloadDeserializer(); } @Test @@ -68,7 +71,7 @@ public void shouldThrowWhenParsingArrayWithObjectValue() throws Exception { ArrayNode arrNode = new ArrayNode(JsonNodeFactory.instance, subNodes); tree.put("key", arrNode); - deserializer.getStringOrArray(tree, "key"); + deserializer.getStringOrArray(objectMapper, tree, "key"); } @Test @@ -123,7 +126,7 @@ public void shouldGetStringArrayWhenParsingArrayNode() { ArrayNode arrNode = new ArrayNode(JsonNodeFactory.instance, subNodes); tree.put("key", arrNode); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(notNullValue())); assertThat(values, is(IsCollectionWithSize.hasSize(2))); assertThat(values, is(IsIterableContaining.hasItems("one", "two"))); @@ -135,7 +138,7 @@ public void shouldGetStringArrayWhenParsingTextNode() { TextNode textNode = new TextNode("something"); tree.put("key", textNode); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(notNullValue())); assertThat(values, is(IsCollectionWithSize.hasSize(1))); assertThat(values, is(IsIterableContaining.hasItems("something"))); @@ -147,7 +150,7 @@ public void shouldGetEmptyStringArrayWhenParsingEmptyTextNode() { TextNode textNode = new TextNode(""); tree.put("key", textNode); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(notNullValue())); assertThat(values, is(IsEmptyCollection.empty())); } @@ -158,7 +161,7 @@ public void shouldGetNullArrayWhenParsingNullNode() { NullNode node = NullNode.getInstance(); tree.put("key", node); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(nullValue())); } @@ -167,7 +170,7 @@ public void shouldGetNullArrayWhenParsingNullNodeValue() { Map tree = new HashMap<>(); tree.put("key", null); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(nullValue())); } @@ -177,7 +180,7 @@ public void shouldGetNullArrayWhenParsingNonArrayOrTextNode() { IntNode node = new IntNode(456789); tree.put("key", node); - List values = deserializer.getStringOrArray(tree, "key"); + List values = deserializer.getStringOrArray(objectMapper, tree, "key"); assertThat(values, is(nullValue())); } diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java index da0c880e..5ad7ac68 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadImplTest.java @@ -29,29 +29,28 @@ public class PayloadImplTest { private final Instant notBefore = Instant.now(); private final Instant issuedAt = Instant.now(); - private ObjectReader objectReader; + private ObjectMapper objectMapper; @Before public void setUp() { - ObjectMapper mapper = getDefaultObjectMapper(); - objectReader = mapper.reader(); + objectMapper = getDefaultObjectMapper(); Map tree = new HashMap<>(); tree.put("extraClaim", new TextNode("extraValue")); - payload = new PayloadImpl("issuer", "subject", Collections.singletonList("audience"), expiresAt, notBefore, issuedAt, "jwtId", tree, objectReader); + payload = new PayloadImpl("issuer", "subject", Collections.singletonList("audience"), expiresAt, notBefore, issuedAt, "jwtId", tree, objectMapper); } @Test public void shouldHaveUnmodifiableTree() { exception.expect(UnsupportedOperationException.class); - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, new HashMap<>(), objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, new HashMap<>(), objectMapper); payload.getTree().put("something", null); } @Test public void shouldHaveUnmodifiableAudience() { exception.expect(UnsupportedOperationException.class); - PayloadImpl payload = new PayloadImpl(null, null, new ArrayList<>(), null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, new ArrayList<>(), null, null, null, null, null, objectMapper); payload.getAudience().add("something"); } @@ -63,7 +62,7 @@ public void shouldGetIssuer() { @Test public void shouldGetNullIssuerIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getIssuer(), is(nullValue())); } @@ -76,7 +75,7 @@ public void shouldGetSubject() { @Test public void shouldGetNullSubjectIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getSubject(), is(nullValue())); } @@ -91,7 +90,7 @@ public void shouldGetAudience() { @Test public void shouldGetNullAudienceIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getAudience(), is(nullValue())); } @@ -105,7 +104,7 @@ public void shouldGetExpiresAt() { @Test public void shouldGetNullExpiresAtIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getExpiresAt(), is(nullValue())); assertThat(payload.getExpiresAtAsInstant(), is(nullValue())); @@ -120,7 +119,7 @@ public void shouldGetNotBefore() { @Test public void shouldGetNullNotBeforeIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getNotBefore(), is(nullValue())); assertThat(payload.getNotBeforeAsInstant(), is(nullValue())); @@ -135,7 +134,7 @@ public void shouldGetIssuedAt() { @Test public void shouldGetNullIssuedAtIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getIssuedAt(), is(nullValue())); assertThat(payload.getIssuedAtAsInstant(), is(nullValue())); @@ -149,7 +148,7 @@ public void shouldGetJWTId() { @Test public void shouldGetNullJWTIdIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getId(), is(nullValue())); } @@ -163,7 +162,7 @@ public void shouldGetExtraClaim() { @Test public void shouldGetNotNullExtraClaimIfMissing() { - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, null, objectMapper); assertThat(payload, is(notNullValue())); assertThat(payload.getClaim("missing"), is(notNullValue())); assertThat(payload.getClaim("missing").isMissing(), is(true)); @@ -175,7 +174,7 @@ public void shouldGetClaims() { Map tree = new HashMap<>(); tree.put("extraClaim", new TextNode("extraValue")); tree.put("sub", new TextNode("auth0")); - PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, tree, objectReader); + PayloadImpl payload = new PayloadImpl(null, null, null, null, null, null, null, tree, objectMapper); assertThat(payload, is(notNullValue())); Map claims = payload.getClaims(); assertThat(claims, is(notNullValue())); diff --git a/settings.gradle b/settings.gradle index a4cda2d7..8d5f112c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,4 +8,4 @@ pluginManagement { } include ':java-jwt' -project(':java-jwt').projectDir = new File(rootProject.projectDir, '/lib') \ No newline at end of file +project(':java-jwt').projectDir = new File(rootProject.projectDir, '/lib')