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