From 0b7f9e0d741e32dd2c7db2b72a6e1325121343b8 Mon Sep 17 00:00:00 2001 From: Daniel DeGroff Date: Thu, 21 May 2026 13:08:53 -0600 Subject: [PATCH 1/2] sign test From ae785b4244379b8e82ad1b21e6bf22a3c2529a91 Mon Sep 17 00:00:00 2001 From: Daniel DeGroff Date: Thu, 21 May 2026 15:53:07 -0600 Subject: [PATCH 2/2] Remove JWTDecoder.Builder.fixedTime convenience clock(Clock) already exists as the primary API for overriding the decoder's notion of "now". Call sites use clock(Clock.fixed(instant, ZoneOffset.UTC)) directly, matching the JDK's Clock vocabulary. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 6 +++--- .../lattejwtjackson/LatteJWTJacksonAdapter.java | 10 ++++++---- .../jwt/benchmarks/lattejwt/LatteJWTAdapter.java | 10 ++++++---- src/main/java/org/lattejava/jwt/JWT.java | 4 ++-- src/main/java/org/lattejava/jwt/JWTDecoder.java | 11 +---------- src/test/java/org/lattejava/jwt/JWTTest.java | 6 +++--- .../java/org/lattejava/jwt/RFC7515VectorsTest.java | 3 ++- 7 files changed, 23 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index cec6370..72f3c14 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ The recommended entry points are the `Signers` and `Verifiers` factories. They t JWTs are built with an immutable fluent builder and serialized by a `JWTEncoder`. Decoding is done by a `JWTDecoder`, which takes a `VerifierResolver` — use `VerifierResolver.byKid(Map)` for a `kid`-indexed keyring, or `VerifierResolver.of(verifier)` to wrap a single verifier. -For the happy path, `JWT.decode(encodedJWT, verifier)` (and an `(encodedJWT, verifier, validator)` overload) routes through a shared default `JWTDecoder` so you don't have to construct one. The same overloads accept a `VerifierResolver` when you have a keyring. Build your own `JWTDecoder` with `JWTDecoder.builder()` when you need non-default settings — a custom `JSONProcessor`, `clockSkew`, allowed algorithms, `fixedTime`, etc. — and pass it to the `JWT.decode(encodedJWT, decoder, verifier)` overload (or call `decoder.decode(...)` directly). +For the happy path, `JWT.decode(encodedJWT, verifier)` (and an `(encodedJWT, verifier, validator)` overload) routes through a shared default `JWTDecoder` so you don't have to construct one. The same overloads accept a `VerifierResolver` when you have a keyring. Build your own `JWTDecoder` with `JWTDecoder.builder()` when you need non-default settings — a custom `JSONProcessor`, `clockSkew`, allowed algorithms, a custom `Clock`, etc. — and pass it to the `JWT.decode(encodedJWT, decoder, verifier)` overload (or call `decoder.decode(...)` directly). #### Sign and encode a JWT using HMAC ```java @@ -175,7 +175,7 @@ Alternate: `ECVerifier.newVerifier(Paths.get("public_key.pem"))`. #### Build your own JWTDecoder -The `JWT.decode(...)` static helpers route through a shared default decoder that uses the bundled zero-dependency `LatteJSONProcessor`. Build your own when you need a different `JSONProcessor` or any other non-default setting — `clockSkew`, `expectedAlgorithms`, `expectedType`, `criticalHeaders`, `maxInputBytes`, `fixedTime`, etc. The clock-skew and time-pinning examples below use the same builder. +The `JWT.decode(...)` static helpers route through a shared default decoder that uses the bundled zero-dependency `LatteJSONProcessor`. Build your own when you need a different `JSONProcessor` or any other non-default setting — `clockSkew`, `expectedAlgorithms`, `expectedType`, `criticalHeaders`, `maxInputBytes`, a custom `Clock`, etc. The clock-skew and time-pinning examples below use the same builder. `JSONProcessor` is a strategy interface — `serialize(Map)` / `deserialize(byte[])` — and **the library does not bundle a Jackson, Gson, or other third-party adapter**. To use one, write a small class that implements `JSONProcessor` and delegates to your JSON library of choice. Implementations must be stateless and thread-safe; the encoder and decoder may call them concurrently. @@ -291,7 +291,7 @@ Verifier verifier = Verifiers.forAsymmetric(Algorithm.ES256, // Pin the decoder to a specific instant. Use ONLY in tests — never in production. Instant thePast = Instant.parse("2019-01-01T00:00:00Z"); JWTDecoder timeMachine = JWTDecoder.builder() - .fixedTime(thePast) + .clock(Clock.fixed(thePast, ZoneOffset.UTC)) .build(); JWT jwt = timeMachine.decode(encodedJWT, VerifierResolver.of(verifier)); diff --git a/benchmarks/vendors/latte-jwt-jackson/src/main/java/org/lattejava/jwt/benchmarks/lattejwtjackson/LatteJWTJacksonAdapter.java b/benchmarks/vendors/latte-jwt-jackson/src/main/java/org/lattejava/jwt/benchmarks/lattejwtjackson/LatteJWTJacksonAdapter.java index ea3f957..293a895 100644 --- a/benchmarks/vendors/latte-jwt-jackson/src/main/java/org/lattejava/jwt/benchmarks/lattejwtjackson/LatteJWTJacksonAdapter.java +++ b/benchmarks/vendors/latte-jwt-jackson/src/main/java/org/lattejava/jwt/benchmarks/lattejwtjackson/LatteJWTJacksonAdapter.java @@ -4,7 +4,9 @@ */ package org.lattejava.jwt.benchmarks.lattejwtjackson; +import java.time.Clock; import java.time.Instant; +import java.time.ZoneOffset; import org.lattejava.jwt.Algorithm; import org.lattejava.jwt.JSONProcessor; @@ -55,10 +57,10 @@ public void prepare(Fixtures fixtures) throws Exception { encoder = new JWTEncoder(json); - Instant fixedNow = Instant.ofEpochSecond(1761408000L + 1800L); - es256Decoder = JWTDecoder.builder().fixedTime(fixedNow).jsonProcessor(json).build(); - hs256Decoder = JWTDecoder.builder().fixedTime(fixedNow).jsonProcessor(json).build(); - rs256Decoder = JWTDecoder.builder().fixedTime(fixedNow).jsonProcessor(json).build(); + Clock fixedClock = Clock.fixed(Instant.ofEpochSecond(1761408000L + 1800L), ZoneOffset.UTC); + es256Decoder = JWTDecoder.builder().clock(fixedClock).jsonProcessor(json).build(); + hs256Decoder = JWTDecoder.builder().clock(fixedClock).jsonProcessor(json).build(); + rs256Decoder = JWTDecoder.builder().clock(fixedClock).jsonProcessor(json).build(); unsafeDecoder = JWTDecoder.builder().jsonProcessor(json).build(); canonicalJWT = JWT.builder() diff --git a/benchmarks/vendors/latte-jwt/src/main/java/org/lattejava/jwt/benchmarks/lattejwt/LatteJWTAdapter.java b/benchmarks/vendors/latte-jwt/src/main/java/org/lattejava/jwt/benchmarks/lattejwt/LatteJWTAdapter.java index 41714b6..0b3ccda 100644 --- a/benchmarks/vendors/latte-jwt/src/main/java/org/lattejava/jwt/benchmarks/lattejwt/LatteJWTAdapter.java +++ b/benchmarks/vendors/latte-jwt/src/main/java/org/lattejava/jwt/benchmarks/lattejwt/LatteJWTAdapter.java @@ -4,7 +4,9 @@ */ package org.lattejava.jwt.benchmarks.lattejwt; +import java.time.Clock; import java.time.Instant; +import java.time.ZoneOffset; import org.lattejava.jwt.Algorithm; import org.lattejava.jwt.JWT; @@ -46,10 +48,10 @@ public void prepare(Fixtures fixtures) throws Exception { encoder = new JWTEncoder(); - Instant fixedNow = Instant.ofEpochSecond(1761408000L + 1800L); - es256Decoder = JWTDecoder.builder().fixedTime(fixedNow).build(); - hs256Decoder = JWTDecoder.builder().fixedTime(fixedNow).build(); - rs256Decoder = JWTDecoder.builder().fixedTime(fixedNow).build(); + Clock fixedClock = Clock.fixed(Instant.ofEpochSecond(1761408000L + 1800L), ZoneOffset.UTC); + es256Decoder = JWTDecoder.builder().clock(fixedClock).build(); + hs256Decoder = JWTDecoder.builder().clock(fixedClock).build(); + rs256Decoder = JWTDecoder.builder().clock(fixedClock).build(); unsafeDecoder = JWTDecoder.builder().build(); canonicalJWT = JWT.builder() diff --git a/src/main/java/org/lattejava/jwt/JWT.java b/src/main/java/org/lattejava/jwt/JWT.java index 479d59d..b5eb564 100644 --- a/src/main/java/org/lattejava/jwt/JWT.java +++ b/src/main/java/org/lattejava/jwt/JWT.java @@ -98,7 +98,7 @@ public static Builder builder() { * be observed until the signature has been validated. * *

Build your own decoder with {@link JWTDecoder#builder()} when you need non-default settings (custom - * {@link JSONProcessor}, {@code clockSkew}, allowed algorithms, {@code fixedTime}, etc.).

+ * {@link JSONProcessor}, {@code clockSkew}, allowed algorithms, custom {@link java.time.Clock}, etc.).

* * @param encodedJWT the compact JWS string; must be non-null * @param resolver the verifier resolver; must be non-null @@ -151,7 +151,7 @@ public static JWT decode(String encodedJWT, JWTDecoder decoder, Verifier verifie * subclass to reject the token. * *

Build your own decoder with {@link JWTDecoder#builder()} when you need non-default settings (custom - * {@link JSONProcessor}, {@code clockSkew}, allowed algorithms, {@code fixedTime}, etc.).

+ * {@link JSONProcessor}, {@code clockSkew}, allowed algorithms, custom {@link java.time.Clock}, etc.).

* * @param encodedJWT the compact JWS string; must be non-null * @param resolver the verifier resolver; must be non-null diff --git a/src/main/java/org/lattejava/jwt/JWTDecoder.java b/src/main/java/org/lattejava/jwt/JWTDecoder.java index a1b8e8c..321f135 100644 --- a/src/main/java/org/lattejava/jwt/JWTDecoder.java +++ b/src/main/java/org/lattejava/jwt/JWTDecoder.java @@ -139,7 +139,7 @@ public static Builder builder() { /** * Returns the shared default {@link JWTDecoder} used by {@link JWT#decode(String, VerifierResolver)} and its * overloads. Build your own with {@link #builder()} when you need non-default settings (custom {@link JSONProcessor}, - * {@code clockSkew}, allowed algorithms, {@code fixedTime}, etc.). + * {@code clockSkew}, allowed algorithms, custom {@link Clock}, etc.). * * @return the shared default instance; never {@code null} */ @@ -550,15 +550,6 @@ public Builder expectedType(String expectedType) { return this; } - /** - * Convenience for {@code clock(Clock.fixed(instant, ZoneOffset.UTC))}. - */ - public Builder fixedTime(Instant instant) { - Objects.requireNonNull(instant, "instant"); - this.clock = Clock.fixed(instant, ZoneOffset.UTC); - return this; - } - public Builder jsonProcessor(JSONProcessor jsonProcessor) { this.jsonProcessor = Objects.requireNonNull(jsonProcessor, "jsonProcessor"); this.jsonProcessorExplicit = true; diff --git a/src/test/java/org/lattejava/jwt/JWTTest.java b/src/test/java/org/lattejava/jwt/JWTTest.java index 7e33a88..7e476a3 100644 --- a/src/test/java/org/lattejava/jwt/JWTTest.java +++ b/src/test/java/org/lattejava/jwt/JWTTest.java @@ -308,9 +308,9 @@ public void decode_single_verifier_shortcut_with_validator() { @Test public void decode_uses_supplied_decoder() { - // Use case: JWT.decode(encodedJWT, decoder, resolver) routes through the supplied decoder. We confirm by - // pinning fixedTime far in the future and asserting the call would fail without our supplied clock skew -- - // proving the supplied decoder (not the default) was consulted. + // Use case: JWT.decode(encodedJWT, decoder, resolver) routes through the supplied decoder. We confirm by pinning + // the clock far in the future and asserting the call would fail without our supplied clock skew -- proving the + // supplied decoder (not the default) was consulted. JWT jwt = JWT.builder() .subject("supplied-decoder") .expiresAt(Instant.parse("2026-04-26T12:00:00Z")) diff --git a/src/test/java/org/lattejava/jwt/RFC7515VectorsTest.java b/src/test/java/org/lattejava/jwt/RFC7515VectorsTest.java index 9f614c4..e088b37 100644 --- a/src/test/java/org/lattejava/jwt/RFC7515VectorsTest.java +++ b/src/test/java/org/lattejava/jwt/RFC7515VectorsTest.java @@ -8,6 +8,7 @@ import java.math.*; import java.security.*; import java.security.spec.*; +import java.time.*; import java.util.*; import org.lattejava.jwt.algorithm.ec.*; @@ -97,7 +98,7 @@ private static PublicKey rsaPublic(BigInteger n, BigInteger e) { */ private static JWTDecoder vectorClockDecoder() { return JWTDecoder.builder() - .fixedTime(java.time.Instant.ofEpochSecond(1300819300L)) + .clock(Clock.fixed(Instant.ofEpochSecond(1300819300L), ZoneOffset.UTC)) .build(); }