Skip to content

Commit

Permalink
Replaced JwtParserBuilder#add* methods with new collection builder me…
Browse files Browse the repository at this point in the history
…thods: enc(), key(), sig() and zip()
  • Loading branch information
lhazlewood committed Sep 29, 2023
1 parent cae452a commit 0913219
Show file tree
Hide file tree
Showing 27 changed files with 470 additions and 444 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
*/
package io.jsonwebtoken;

import java.util.Collection;

/**
* Looks for a JWT {@code zip} header, and if found, returns the corresponding {@link CompressionCodec} the parser
* can use to decompress the JWT body.
Expand All @@ -31,9 +29,9 @@
* {@link io.jsonwebtoken.JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.</p>
*
* @see JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver)
* @see JwtParserBuilder#addCompressionAlgorithms(Collection)
* @see JwtParserBuilder#zip()
* @since 0.6.0
* @deprecated in favor of {@link JwtParserBuilder#addCompressionAlgorithms(Collection)}
* @deprecated in favor of {@link JwtParserBuilder#zip()}
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
Expand Down
203 changes: 130 additions & 73 deletions api/src/main/java/io/jsonwebtoken/JwtParserBuilder.java

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions api/src/main/java/io/jsonwebtoken/io/CompressionAlgorithm.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@
package io.jsonwebtoken.io;

import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParserBuilder;
import io.jsonwebtoken.Jwts;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;

/**
* Compresses and decompresses byte streams.
Expand All @@ -32,10 +33,12 @@
* <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.3"><code>zip</code></a> header value.</p>
*
* <p><b>Custom Implementations</b></p>
*
* <p>A custom implementation of this interface may be used when creating a JWT by calling the
* {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionAlgorithm)} method. To ensure that parsing is
* possible, the parser must be aware of the implementation by calling
* {@link io.jsonwebtoken.JwtParserBuilder#addCompressionAlgorithms(Collection)} during parser construction.</p>
* {@link JwtBuilder#compressWith(CompressionAlgorithm)} method.</p>
*
* <p>To ensure that parsing is possible, the parser must be aware of the implementation by adding it to the
* {@link JwtParserBuilder#zip()} collection during parser construction.</p>
*
* @see Jwts.ZIP#DEF
* @see Jwts.ZIP#GZIP
Expand Down
36 changes: 18 additions & 18 deletions impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.DateFormats;
import io.jsonwebtoken.lang.Objects;
import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.DecryptAeadRequest;
Expand Down Expand Up @@ -188,13 +189,13 @@ public class DefaultJwtParser implements JwtParser {

private final boolean unsecuredDecompression;

private final Function<JwsHeader, SecureDigestAlgorithm<?, ?>> sigAlgFn;
private final Function<JwsHeader, SecureDigestAlgorithm<?, ?>> sigAlgs;

private final Function<JweHeader, AeadAlgorithm> encAlgFn;
private final Function<JweHeader, AeadAlgorithm> encAlgs;

private final Function<JweHeader, KeyAlgorithm<?, ?>> keyAlgFn;
private final Function<JweHeader, KeyAlgorithm<?, ?>> keyAlgs;

private final Function<Header, CompressionAlgorithm> zipAlgFn;
private final Function<Header, CompressionAlgorithm> zipAlgs;

private final Locator<? extends Key> keyLocator;

Expand Down Expand Up @@ -224,10 +225,10 @@ public class DefaultJwtParser implements JwtParser {
Decoder<InputStream, InputStream> base64UrlDecoder,
Deserializer<Map<String, ?>> deserializer,
CompressionCodecResolver compressionCodecResolver,
Collection<CompressionAlgorithm> extraZipAlgs,
Collection<SecureDigestAlgorithm<?, ?>> extraSigAlgs,
Collection<KeyAlgorithm<?, ?>> extraKeyAlgs,
Collection<AeadAlgorithm> extraEncAlgs) {
Registry<String, CompressionAlgorithm> zipAlgs,
Registry<String, SecureDigestAlgorithm<?, ?>> sigAlgs,
Registry<String, KeyAlgorithm<?, ?>> keyAlgs,
Registry<String, AeadAlgorithm> encAlgs) {
this.provider = provider;
this.unsecured = unsecured;
this.unsecuredDecompression = unsecuredDecompression;
Expand All @@ -239,12 +240,11 @@ public class DefaultJwtParser implements JwtParser {
this.expectedClaims = Jwts.claims().add(expectedClaims);
this.decoder = Assert.notNull(base64UrlDecoder, "base64UrlDecoder cannot be null.");
this.deserializer = Assert.notNull(deserializer, "JSON Deserializer cannot be null.");

this.sigAlgFn = new IdLocator<>(DefaultHeader.ALGORITHM, Jwts.SIG.get(), extraSigAlgs, MISSING_JWS_ALG_MSG);
this.keyAlgFn = new IdLocator<>(DefaultHeader.ALGORITHM, Jwts.KEY.get(), extraKeyAlgs, MISSING_JWE_ALG_MSG);
this.encAlgFn = new IdLocator<>(DefaultJweHeader.ENCRYPTION_ALGORITHM, Jwts.ENC.get(), extraEncAlgs, MISSING_ENC_MSG);
this.zipAlgFn = compressionCodecResolver != null ? new CompressionCodecLocator(compressionCodecResolver) :
new IdLocator<>(DefaultHeader.COMPRESSION_ALGORITHM, Jwts.ZIP.get(), extraZipAlgs, null);
this.sigAlgs = new IdLocator<>(DefaultHeader.ALGORITHM, sigAlgs, MISSING_JWS_ALG_MSG);
this.keyAlgs = new IdLocator<>(DefaultHeader.ALGORITHM, keyAlgs, MISSING_JWE_ALG_MSG);
this.encAlgs = new IdLocator<>(DefaultJweHeader.ENCRYPTION_ALGORITHM, encAlgs, MISSING_ENC_MSG);
this.zipAlgs = compressionCodecResolver != null ? new CompressionCodecLocator(compressionCodecResolver) :
new IdLocator<>(DefaultHeader.COMPRESSION_ALGORITHM, zipAlgs, null);
}

@Override
Expand All @@ -271,7 +271,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe

SecureDigestAlgorithm<?, Key> algorithm;
try {
algorithm = (SecureDigestAlgorithm<?, Key>) sigAlgFn.apply(jwsHeader);
algorithm = (SecureDigestAlgorithm<?, Key>) sigAlgs.apply(jwsHeader);
} catch (UnsupportedJwtException e) {
//For backwards compatibility. TODO: remove this try/catch block for 1.0 and let UnsupportedJwtException propagate
String msg = "Unsupported signature algorithm '" + alg + "'";
Expand Down Expand Up @@ -531,10 +531,10 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
if (!Strings.hasText(enc)) {
throw new MalformedJwtException(MISSING_ENC_MSG);
}
final AeadAlgorithm encAlg = this.encAlgFn.apply(jweHeader);
final AeadAlgorithm encAlg = this.encAlgs.apply(jweHeader);
Assert.stateNotNull(encAlg, "JWE Encryption Algorithm cannot be null.");

@SuppressWarnings("rawtypes") final KeyAlgorithm keyAlg = this.keyAlgFn.apply(jweHeader);
@SuppressWarnings("rawtypes") final KeyAlgorithm keyAlg = this.keyAlgs.apply(jweHeader);
Assert.stateNotNull(keyAlg, "JWE Key Algorithm cannot be null.");

Key key = this.keyLocator.locate(jweHeader);
Expand Down Expand Up @@ -578,7 +578,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
integrityVerified = true; // no exception means signature verified
}

final CompressionAlgorithm compressionAlgorithm = zipAlgFn.apply(header);
final CompressionAlgorithm compressionAlgorithm = zipAlgs.apply(header);
if (compressionAlgorithm != null) {
if (!integrityVerified) {
if (!payloadBase64UrlEncoded) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,22 @@
import io.jsonwebtoken.Locator;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.impl.io.DelegateStringDecoder;
import io.jsonwebtoken.impl.io.StandardCompressionAlgorithms;
import io.jsonwebtoken.impl.lang.DefaultNestedCollection;
import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.impl.lang.Services;
import io.jsonwebtoken.impl.security.ConstantKeyLocator;
import io.jsonwebtoken.impl.security.StandardEncryptionAlgorithms;
import io.jsonwebtoken.impl.security.StandardKeyAlgorithms;
import io.jsonwebtoken.impl.security.StandardSecureDigestAlgorithms;
import io.jsonwebtoken.io.CompressionAlgorithm;
import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Deserializer;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.NestedCollection;
import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.KeyAlgorithm;
Expand All @@ -46,9 +52,7 @@
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

Expand Down Expand Up @@ -80,17 +84,18 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
@SuppressWarnings("deprecation") //TODO: remove for 1.0
private SigningKeyResolver signingKeyResolver = null;

private final Collection<AeadAlgorithm> extraEncAlgs = new LinkedHashSet<>();
private Registry<String, AeadAlgorithm> encAlgs = Jwts.ENC.get();

private final Collection<KeyAlgorithm<?, ?>> extraKeyAlgs = new LinkedHashSet<>();
private Registry<String, KeyAlgorithm<?, ?>> keyAlgs = Jwts.KEY.get();

private final Collection<SecureDigestAlgorithm<?, ?>> extraSigAlgs = new LinkedHashSet<>();
private Registry<String, SecureDigestAlgorithm<?, ?>> sigAlgs = Jwts.SIG.get();

private final Collection<CompressionAlgorithm> extraZipAlgs = new LinkedHashSet<>();
private Registry<String, CompressionAlgorithm> zipAlgs = Jwts.ZIP.get();

@SuppressWarnings("deprecation")
private CompressionCodecResolver compressionCodecResolver;

@SuppressWarnings("deprecation")
private Decoder<InputStream, InputStream> decoder = new DelegateStringDecoder(Decoders.BASE64URL);

private Deserializer<Map<String, ?>> deserializer;
Expand Down Expand Up @@ -296,31 +301,47 @@ private JwtParserBuilder decryptWith(final Key key) {
}

@Override
public JwtParserBuilder addCompressionAlgorithms(Collection<? extends CompressionAlgorithm> algs) {
Assert.notEmpty(algs, "Additional CompressionAlgorithm collection cannot be null or empty.");
this.extraZipAlgs.addAll(algs);
return this;
public NestedCollection<CompressionAlgorithm, JwtParserBuilder> zip() {
return new DefaultNestedCollection<CompressionAlgorithm, JwtParserBuilder>(this, this.zipAlgs.values()) {
@Override
public JwtParserBuilder and() {
zipAlgs = new IdRegistry<>(StandardCompressionAlgorithms.NAME, getCollection());
return super.and();
}
};
}

@Override
public JwtParserBuilder addEncryptionAlgorithms(Collection<? extends AeadAlgorithm> algs) {
Assert.notEmpty(algs, "Additional AeadAlgorithm collection cannot be null or empty.");
this.extraEncAlgs.addAll(algs);
return this;
public NestedCollection<AeadAlgorithm, JwtParserBuilder> enc() {
return new DefaultNestedCollection<AeadAlgorithm, JwtParserBuilder>(this, this.encAlgs.values()) {
@Override
public JwtParserBuilder and() {
encAlgs = new IdRegistry<>(StandardEncryptionAlgorithms.NAME, getCollection());
return super.and();
}
};
}

@Override
public JwtParserBuilder addSignatureAlgorithms(Collection<? extends SecureDigestAlgorithm<?, ?>> algs) {
Assert.notEmpty(algs, "Additional SignatureAlgorithm collection cannot be null or empty.");
this.extraSigAlgs.addAll(algs);
return this;
public NestedCollection<SecureDigestAlgorithm<?, ?>, JwtParserBuilder> sig() {
return new DefaultNestedCollection<SecureDigestAlgorithm<?, ?>, JwtParserBuilder>(this, this.sigAlgs.values()) {
@Override
public JwtParserBuilder and() {
sigAlgs = new IdRegistry<>(StandardSecureDigestAlgorithms.NAME, getCollection());
return super.and();
}
};
}

@Override
public JwtParserBuilder addKeyAlgorithms(Collection<? extends KeyAlgorithm<?, ?>> algs) {
Assert.notEmpty(algs, "Additional KeyAlgorithm collection cannot be null or empty.");
this.extraKeyAlgs.addAll(algs);
return this;
public NestedCollection<KeyAlgorithm<?, ?>, JwtParserBuilder> key() {
return new DefaultNestedCollection<KeyAlgorithm<?, ?>, JwtParserBuilder>(this, this.keyAlgs.values()) {
@Override
public JwtParserBuilder and() {
keyAlgs = new IdRegistry<>(StandardKeyAlgorithms.NAME, getCollection());
return super.and();
}
};
}

@SuppressWarnings("deprecation") //TODO: remove for 1.0
Expand Down Expand Up @@ -380,9 +401,9 @@ public JwtParser build() {
"due to their security implications.";
throw new IllegalStateException(msg);
}
if (this.compressionCodecResolver != null && !Collections.isEmpty(extraZipAlgs)) {
String msg = "Both 'addCompressionAlgorithms' and 'compressionCodecResolver' " +
"cannot be specified. Choose either.";
if (this.compressionCodecResolver != null && !Jwts.ZIP.get().equals(this.zipAlgs)) {
String msg = "Both 'zip()' and 'compressionCodecResolver' " +
"cannot be configured. Choose either.";
throw new IllegalStateException(msg);
}

Expand All @@ -404,10 +425,10 @@ public JwtParser build() {
decoder,
deserializer,
compressionCodecResolver,
extraZipAlgs,
extraSigAlgs,
extraKeyAlgs,
extraEncAlgs
zipAlgs,
sigAlgs,
keyAlgs,
encAlgs
);
}
}
25 changes: 2 additions & 23 deletions impl/src/main/java/io/jsonwebtoken/impl/IdLocator.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,11 @@
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.impl.lang.Function;
import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.impl.lang.Parameter;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.lang.Strings;

import java.util.Collection;
import java.util.LinkedHashSet;

public class IdLocator<H extends Header, R extends Identifiable> implements Locator<R>, Function<H, R> {

private final Parameter<String> param;
Expand All @@ -41,28 +36,12 @@ public class IdLocator<H extends Header, R extends Identifiable> implements Loca

private final Registry<String, R> registry;

public IdLocator(Parameter<String> param, Registry<String, R> registry, Collection<R> extras, String requiredExceptionMessage) {
public IdLocator(Parameter<String> param, Registry<String, R> registry, String requiredExceptionMessage) {
this.param = Assert.notNull(param, "Header param cannot be null.");
this.requiredMsg = Strings.clean(requiredExceptionMessage);
this.valueRequired = Strings.hasText(this.requiredMsg);
Assert.notEmpty(registry, "Registry cannot be null or empty.");
Collection<R> all = new LinkedHashSet<>(Collections.size(registry) + Collections.size(extras));
all.addAll(registry.values()); // defaults MUST come before extras to allow extras to override if necessary
all.addAll(extras);

// The registry requires CaSe-SeNsItIvE keys on purpose - all JWA standard algorithm identifiers
// (JWS 'alg', JWE 'enc', JWK 'kty', etc) are all case-sensitive per via the following RFC language:
//
// This name is a case-sensitive ASCII string. Names may not match other registered names in a
// case-insensitive manner unless the Designated Experts state that there is a compelling reason to
// allow an exception.
//
// References:
// - JWS/JWE alg and JWE enc 'Algorithm Name': https://www.rfc-editor.org/rfc/rfc7518.html#section-7.1.1
// - JWE zip 'Compression Algorithm Value': https://www.rfc-editor.org/rfc/rfc7518.html#section-7.3.1
// - JWK '"kty" Parameter Value': https://www.rfc-editor.org/rfc/rfc7518.html#section-7.4.1

this.registry = new IdRegistry<>(param.getName(), all); // do not use the caseSensitive ctor argument - must be false
this.registry = registry;
}

private static String type(Header header) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@

import io.jsonwebtoken.impl.compression.DeflateCompressionAlgorithm;
import io.jsonwebtoken.impl.compression.GzipCompressionAlgorithm;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.io.CompressionAlgorithm;
import io.jsonwebtoken.lang.Collections;

@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.ZIP
public final class StandardCompressionAlgorithms extends DelegatingRegistry<String, CompressionAlgorithm> {
public final class StandardCompressionAlgorithms extends IdRegistry<CompressionAlgorithm> {

public static final String NAME = "Compression Algorithm";

public StandardCompressionAlgorithms() {
super(new IdRegistry<>("Compression Algorithm", Collections.<CompressionAlgorithm>of(
super(NAME, Collections.<CompressionAlgorithm>of(
new DeflateCompressionAlgorithm(),
new GzipCompressionAlgorithm()
), false));
));
}
}
21 changes: 21 additions & 0 deletions impl/src/main/java/io/jsonwebtoken/impl/lang/DefaultRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,25 @@ public void putAll(Map<? extends K, ? extends V> m) {
public void clear() {
immutable();
}

@Override
public int hashCode() {
return DELEGATE.hashCode();
}

@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj instanceof DefaultRegistry) {
DefaultRegistry<?, ?> other = (DefaultRegistry<?, ?>) obj;
return this.qualifiedKeyName.equals(other.qualifiedKeyName) &&
this.DELEGATE.equals(other.DELEGATE);
}
return false;
}

@Override
public String toString() {
return DELEGATE.toString();
}
}
Loading

0 comments on commit 0913219

Please sign in to comment.