Skip to content

Commit

Permalink
Immutable jwtparser (#791)
Browse files Browse the repository at this point in the history
* - JwtParser is now immutable (no mutation methods, `final` internal state, etc).
- Jwts.parser() has been changed to return a `JwtParserBuilder`, Jwts.parserBuilder() has been removed since it's now superfluous.
- LegacyServices.java has been deleted since DefaultJwtParser is no longer mutable (it was used in the parse() method. This logic is now only performed via the Services.java implementation used by the JwtParserBuilder).
- ImmutableJwtParser.java has been deleted since it was a wrapper around a mutable DefaultJwtParser implementation.  Now that DefaultJwtParser is immutable, ImmutableJwtParser.java is no longer necessary.
  • Loading branch information
lhazlewood authored Aug 4, 2023
1 parent 529f04d commit 12a4a2e
Show file tree
Hide file tree
Showing 40 changed files with 349 additions and 3,624 deletions.
12 changes: 9 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ AeadAlgorithm enc = Jwts.ENC.A256GCM;
SecretKey key = enc.keyBuilder().build();
String compact = Jwts.builder().setSubject("Joe").encryptWith(key, enc).compact();

Jwe<Claims> jwe = Jwts.parserBuilder().decryptWith(key).build().parseClaimsJwe(compact);
Jwe<Claims> jwe = Jwts.parser().decryptWith(key).build().parseClaimsJwe(compact);
```

Many other RSA and Elliptic Curve examples are in the full README documentation.
Expand Down Expand Up @@ -179,8 +179,14 @@ deprecate some concepts, or in some cases, completely break backwards compatibil
automatically based on builder state.


* `io.jsonwebtoken.Jwts`'s `parser()` method deprecated 4 years ago has been changed to now return a
`JwtParserBuilder` instead of a direct `JwtParser` instance.
* `io.jsonwebtoken.JwtParser` is now immutable. All mutation/modification methods (setters, etc) deprecated 4 years
ago have been removed. All parser configuration requires using the `JwtParserBuilder` (i.e.
`Jwts.parser()`).


* Similarly, `io.jsonwebtoken.Jwts`'s `parser()` method deprecated 4 years ago has been changed to now return a
`JwtParserBuilder` instead of a direct `JwtParser` instance. The previous `Jwts.parserBuilder()` method has been
removed as it is now redundant.


* `io.jsonwebtoken.CompressionCodec` implementations are no longer discoverable via `java.util.ServiceLoader` due to
Expand Down
84 changes: 42 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60A
Now let's verify the JWT (you should always discard JWTs that don't match an expected signature):

```java
assert Jwts.parserBuilder().verifyWith(key).build().parseClaimsJws(jws).getPayload().getSubject().equals("Joe");
assert Jwts.parser().verifyWith(key).build().parseClaimsJws(jws).getPayload().getSubject().equals("Joe");
```

There are two things going on here. The `key` from before is being used to verify the signature of the JWT. If it
Expand All @@ -737,7 +737,7 @@ But what if parsing or signature validation failed? You can catch `JwtException
```java
try {

Jwts.parserBuilder().verifyWith(key).build().parseClaimsJws(compactJws);
Jwts.parser().verifyWith(key).build().parseClaimsJws(compactJws);

//OK, we can trust this JWT

Expand Down Expand Up @@ -957,7 +957,7 @@ String jws = Jwts.builder()
.setExpiration(expiration) //a java.util.Date
.setNotBefore(notBefore) //a java.util.Date
.setIssuedAt(new Date()) // for example, now
.setId(UUID.randomUUID()) //just an example id
.setId(UUID.randomUUID().toString()) //just an example id

/// ... etc ...
```
Expand Down Expand Up @@ -1047,7 +1047,7 @@ Please see the main [Compression](#compression) section to see how to compress a

You read (parse) a JWT as follows:

1. Use the `Jwts.parserBuilder()` method to create a `JwtParserBuilder` instance.
1. Use the `Jwts.parser()` method to create a `JwtParserBuilder` instance.
2. Optionally call `setKeyLocator`, `verifyWith` or `decryptWith` methods if you expect to parse [signed](#jws) or [encrypted](#jwe) JWTs.
3. Call the `build()` method on the `JwtParserBuilder` to create and return a thread-safe `JwtParser`.
4. Call one of the various `parse*` methods with your compact JWT string, depending on the type of JWT you expect.
Expand All @@ -1059,7 +1059,7 @@ For example:
Jwt<?,?> jwt;

try {
jwt = Jwts.parserBuilder() // (1)
jwt = Jwts.parser() // (1)

.setKeyLocator(keyLocator) // (2) dynamically locate signing or encryption keys
//.verifyWith(key) // or a static key used to verify all signed JWTs
Expand Down Expand Up @@ -1099,7 +1099,7 @@ So which key do we use?
`JwtParserBuilder`. For example:

```java
Jwts.parserBuilder()
Jwts.parser()

.verifyWith(secretKey) // <----

Expand All @@ -1110,7 +1110,7 @@ So which key do we use?
`PrivateKey`) should be specified on the `JwtParserBuilder`. For example:
```java
Jwts.parserBuilder()
Jwts.parser()
.verifyWith(publicKey) // <---- publicKey, not privateKey
Expand All @@ -1121,7 +1121,7 @@ So which key do we use?
specified on the `JwtParserBuilder`. For example:
```java
Jwts.parserBuilder()
Jwts.parser()
.decryptWith(secretKey) // <----
Expand All @@ -1132,7 +1132,7 @@ So which key do we use?
`PrivateKey` (not the `PublicKey`) should be specified on the `JwtParserBuilder`. For example:

```java
Jwts.parserBuilder()
Jwts.parser()

.decryptWith(privateKey) // <---- privateKey, not publicKey

Expand Down Expand Up @@ -1172,7 +1172,7 @@ example:
```java
Locator<Key> keyLocator = getMyKeyLocator();
Jwts.parserBuilder()
Jwts.parser()
.setKeyLocator(keyLocator) // <----
Expand Down Expand Up @@ -1214,10 +1214,10 @@ String keyId = getKeyId(key); //any mechanism you have to associate a key with a

String jws = Jwts.builder()

.setHeader(Jwts.headerBuilder().setKeyId(keyId)) // <--- add `kid` header
.header().setKeyId(keyId).and() // <--- add `kid` header

.signWith(key) // for JWS
//.encryptWith(key, keyAlg, encryptionAlg) // for JWE
.signWith(key) // for JWS
//.encryptWith(key, keyAlg, encryptionAlg) // for JWE
.compact();
```

Expand Down Expand Up @@ -1300,7 +1300,7 @@ otherwise you may not trust the token. You can do that by using one of the vari
```java
try {
Jwts.parserBuilder().requireSubject("jsmith")/* etc... */.build().parse(s);
Jwts.parser().requireSubject("jsmith")/* etc... */.build().parse(s);
} catch (InvalidClaimException ice) {
// the sub field was missing or did not have a 'jsmith' value
}
Expand All @@ -1311,7 +1311,7 @@ you can catch either `MissingClaimException` or `IncorrectClaimException`:
```java
try {
Jwts.parserBuilder().requireSubject("jsmith")/* etc... */.build().parse(s);
Jwts.parser().requireSubject("jsmith")/* etc... */.build().parse(s);
} catch(MissingClaimException mce) {
// the parsed JWT did not have the sub field
} catch(IncorrectClaimException ice) {
Expand All @@ -1323,7 +1323,7 @@ You can also require custom fields by using the `require(fieldName, requiredFiel
```java
try {
Jwts.parserBuilder().require("myfield", "myRequiredValue")/* etc... */.build().parse(s);
Jwts.parser().require("myfield", "myRequiredValue")/* etc... */.build().parse(s);
} catch(InvalidClaimException ice) {
// the 'myfield' field was missing or did not have a 'myRequiredValue' value
}
Expand All @@ -1347,7 +1347,7 @@ You can account for these differences (usually no more than a few minutes) when
```java
long seconds = 3 * 60; //3 minutes

Jwts.parserBuilder()
Jwts.parser()

.setAllowedClockSkewSeconds(seconds) // <----

Expand All @@ -1369,7 +1369,7 @@ during parsing for timestamp comparisons can be obtained via a custom time sourc
```java
Clock clock = new MyClock();
Jwts.parserBuilder().setClock(myClock) //... etc ...
Jwts.parser().setClock(myClock) //... etc ...
```
The `JwtParser`'s default `Clock` implementation simply returns `new Date()` to reflect the time when parsing occurs,
Expand Down Expand Up @@ -1692,7 +1692,7 @@ Please see the main [Compression](#compression) section to see how to compress a

You read (parse) a JWS as follows:

1. Use the `Jwts.parserBuilder()` method to create a `JwtParserBuilder` instance.
1. Use the `Jwts.parser()` method to create a `JwtParserBuilder` instance.
2. Call either [setKeyLocator](#key-locator) or `verifyWith` methods to determine the key used to verify the JWS signature.
3. Call the `build()` method on the `JwtParserBuilder` to return a thread-safe `JwtParser`.
4. Finally, call the `parseClaimsJws(String)` method with your jws `String`, producing the original JWS.
Expand All @@ -1705,7 +1705,7 @@ For example:
Jws<Claims> jws;
try {
jws = Jwts.parserBuilder() // (1)
jws = Jwts.parser() // (1)
.setKeyLocator(keyLocator) // (2) dynamically lookup verification keys based on each JWS
//.verifyWith(key) // or a static key used to verify all encountered JWSs
Expand Down Expand Up @@ -1740,7 +1740,7 @@ So which key do we use for verification?
For example:

```java
Jwts.parserBuilder()
Jwts.parser()

.verifyWith(secretKey) // <----

Expand All @@ -1751,7 +1751,7 @@ For example:
specified on the `JwtParserBuilder`. For example:
```java
Jwts.parserBuilder()
Jwts.parser()
.verifyWith(publicKey) // <---- publicKey, not privateKey
Expand Down Expand Up @@ -2166,7 +2166,7 @@ its size. Please see the main [Compression](#compression) section to see how to
You read (parse) a JWE as follows:
1. Use the `Jwts.parserBuilder()` method to create a `JwtParserBuilder` instance.
1. Use the `Jwts.parser()` method to create a `JwtParserBuilder` instance.
2. Call either [setKeyLocator](#key-locator) or `decryptWith` methods to determine the key used to decrypt the JWE.
4. Call the `JwtParserBuilder`'s `build()` method to create a thread-safe `JwtParser`.
5. Parse the jwe string with the `JwtParser`'s `parseClaimsJwe` or `parseContentJwe` method.
Expand All @@ -2178,7 +2178,7 @@ For example:
Jwe<Claims> jwe;
try {
jwe = Jwts.parserBuilder() // (1)
jwe = Jwts.parser() // (1)
.setKeyLocator(keyLocator) // (2) dynamically lookup decryption keys based on each JWE
//.decryptWith(key) // or a static key used to decrypt all encountered JWEs
Expand Down Expand Up @@ -2212,7 +2212,7 @@ So which key do we use for decryption?
`JwtParserBuilder`. For example:
```java
Jwts.parserBuilder()
Jwts.parser()
.decryptWith(secretKey) // <----
Expand All @@ -2223,7 +2223,7 @@ So which key do we use for decryption?
`Password` must be specified on the `JwtParserBuilder`. For example:
```java
Jwts.parserBuilder()
Jwts.parser()
.decryptWith(password) // <---- an `io.jsonwebtoken.security.Password` instance
Expand All @@ -2234,7 +2234,7 @@ So which key do we use for decryption?
the `PublicKey`) must be specified on the `JwtParserBuilder`. For example:
```java
Jwts.parserBuilder()
Jwts.parser()
.decryptWith(privateKey) // <---- a `PrivateKey`, not a `PublicKey`
Expand Down Expand Up @@ -2583,7 +2583,7 @@ by calling the `JwtParserBuilder`'s `addCompressionCodecs` method. For example:
```java
CompressionCodec myCodec = new MyCompressionCodec();
Jwts.parserBuilder()
Jwts.parser()
.addCompressionCodecs(Collections.of(myCodec)) // <----
Expand Down Expand Up @@ -2629,7 +2629,7 @@ You then provide your custom `Locator<CompressionCodec>` to the `JwtParserBuilde
```java
Locator<CompressionCodec> myCodecLocator = new MyCompressionCodecLocator();

Jwts.parserBuilder()
Jwts.parser()

.setCompressionCodecLocator(myCodecLocator) // <----

Expand Down Expand Up @@ -2694,7 +2694,7 @@ When reading a JWT:
```java
Deserializer<Map<String,?>> deserializer = getMyDeserializer(); //implement me
Jwts.parserBuilder()
Jwts.parser()
.deserializeJsonWith(deserializer)
Expand Down Expand Up @@ -2754,7 +2754,7 @@ and the `JacksonDeserializer` using your `ObjectMapper` on the `JwtParserBuilder
```java
ObjectMapper objectMapper = getMyObjectMapper(); //implement me
Jwts.parserBuilder()
Jwts.parser()
.deserializeJsonWith(new JacksonDeserializer(objectMapper))
Expand Down Expand Up @@ -2786,7 +2786,7 @@ payload of:
The `User` object could be retrieved from the `user` claim with the following code:
```java
Jwts.parserBuilder()
Jwts.parser()
.deserializeJsonWith(new JacksonDeserializer(Maps.of("user", User.class).build())) // <-----
Expand Down Expand Up @@ -3027,7 +3027,7 @@ and the `JwtParserBuilder`'s `base64UrlDecodeWith` method to set the decoder:
```java
Decoder<String, byte[]> base64UrlDecoder = getMyBase64UrlDecoder(); //implement me
Jwts.parserBuilder()
Jwts.parser()
.base64UrlDecodeWith(base64UrlEncoder)
Expand Down Expand Up @@ -3078,7 +3078,7 @@ byte[] content = message.getBytes(StandardCharsets.UTF_8);
String jws = Jwts.builder().setContent(content, "text/plain").signWith(key, alg).compact();
// Parse the compact JWS:
content = Jwts.parserBuilder().verifyWith(key).build().parseContentJws(jws).getPayload();
content = Jwts.parser().verifyWith(key).build().parseContentJws(jws).getPayload();
assert message.equals(new String(content, StandardCharsets.UTF_8));
```
Expand All @@ -3104,7 +3104,7 @@ String jws = Jwts.builder().setSubject("Alice")
.compact();

// Alice receives and verifies the compact JWS came from Bob:
String subject = Jwts.parserBuilder()
String subject = Jwts.parser()
.verifyWith(pair.getPublic()) // <-- Bob's RSA public key
.build().parseClaimsJws(jws).getPayload().getSubject();

Expand Down Expand Up @@ -3135,7 +3135,7 @@ String jws = Jwts.builder().setSubject("Alice")
.compact();

// Alice receives and verifies the compact JWS came from Bob:
String subject = Jwts.parserBuilder()
String subject = Jwts.parser()
.verifyWith(pair.getPublic()) // <-- Bob's EC public key
.build().parseClaimsJws(jws).getPayload().getSubject();

Expand Down Expand Up @@ -3179,7 +3179,7 @@ String jws = Jwts.builder().setSubject("Alice")
.compact();

// Alice receives and verifies the compact JWS came from Bob:
String subject = Jwts.parserBuilder()
String subject = Jwts.parser()
.verifyWith(pair.getPublic()) // <-- Bob's Edwards Curve public key
.build().parseClaimsJws(jws).getPayload().getSubject();

Expand Down Expand Up @@ -3220,7 +3220,7 @@ byte[] content = message.getBytes(StandardCharsets.UTF_8);
String jwe = Jwts.builder().setContent(content, "text/plain").encryptWith(key, enc).compact();

// Parse the compact JWE:
content = Jwts.parserBuilder().decryptWith(key).build().parseContentJwe(jwe).getPayload();
content = Jwts.parser().decryptWith(key).build().parseContentJwe(jwe).getPayload();

assert message.equals(new String(content, StandardCharsets.UTF_8));
```
Expand Down Expand Up @@ -3252,7 +3252,7 @@ String jwe = Jwts.builder().setAudience("Alice")
.compact();

// Alice receives and decrypts the compact JWE:
String audience = Jwts.parserBuilder()
String audience = Jwts.parser()
.decryptWith(pair.getPrivate()) // <-- Alice's RSA private key
.build().parseClaimsJwe(jwe).getPayload().getAudience();

Expand Down Expand Up @@ -3285,7 +3285,7 @@ AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A192GCM, A128GCM, A256CBC-HS512, etc.
String jwe = Jwts.builder().setIssuer("me").encryptWith(key, alg, enc).compact();

// Parse the compact JWE:
String issuer = Jwts.parserBuilder().decryptWith(key).build()
String issuer = Jwts.parser().decryptWith(key).build()
.parseClaimsJwe(jwe).getPayload().getIssuer();

assert "me".equals(issuer);
Expand Down Expand Up @@ -3320,7 +3320,7 @@ String jwe = Jwts.builder().setAudience("Alice")
.compact();

// Alice receives and decrypts the compact JWE:
String audience = Jwts.parserBuilder()
String audience = Jwts.parser()
.decryptWith(pair.getPrivate()) // <-- Alice's EC private key
.build().parseClaimsJwe(jwe).getPayload().getAudience();

Expand Down Expand Up @@ -3367,7 +3367,7 @@ String jwe = Jwts.builder().setIssuer("me")
.compact();

// Parse the compact JWE:
String issuer = Jwts.parserBuilder().decryptWith(password)
String issuer = Jwts.parser().decryptWith(password)
.build().parseClaimsJwe(jwe).getPayload().getIssuer();

assert "me".equals(issuer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* <p>However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you can implement
* your own {@link CompressionCodecResolver} and specify that when
* {@link io.jsonwebtoken.JwtBuilder#compressWith(io.jsonwebtoken.io.CompressionAlgorithm) building} and
* {@link io.jsonwebtoken.JwtParser#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.</p>
* {@link io.jsonwebtoken.JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.</p>
*
* @see JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver)
* @see JwtParserBuilder#addCompressionAlgorithms(Collection)
Expand Down
Loading

0 comments on commit 12a4a2e

Please sign in to comment.