Builder NestedCollection support (#841)

- Added Conjunctor, CollectionMutator, and NestedCollection
- Added JwkBuilder#operations() NestedCollection builder and removed #operation(KeyOperation) and #operations(Collection<KeyOperation>)
- KeyOperationPolicyBuilder now extends CollectionMutator

- Replaced ProtectedHeaderMutator#critical* methods with critical() NestedCollection

- Replaced JwtParserBuilder#critical* methods with critical() NestedCollection

- Replaced ClaimsMutator#audience* methods with audience() AudienceCollection

- Replaced JwtParserBuilder#add* methods with new collection builder methods: enc(), key(), sig() and zip()
This commit is contained in:
lhazlewood 2023-09-29 13:42:36 -07:00 committed by GitHub
parent 20b2fa9d50
commit 854bb8944c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 1215 additions and 817 deletions

View File

@ -15,6 +15,8 @@
*/
package io.jsonwebtoken;
import io.jsonwebtoken.lang.NestedCollection;
import java.util.Collection;
import java.util.Date;
@ -77,61 +79,29 @@ public interface ClaimsMutator<T extends ClaimsMutator<T>> {
* claim</a> as <em>a single String, <b>NOT</b> a String array</em>. This method exists only for producing
* JWTs sent to legacy recipients that are unable to interpret the {@code aud} value as a JSON String Array; it is
* strongly recommended to avoid calling this method whenever possible and favor the
* {@link #audience(String)} or {@link #audience(Collection)} methods instead, as they ensure a single deterministic
* data type for recipients.
* {@link #audience()}.{@link AudienceCollection#add(Object) add(String)} and
* {@link AudienceCollection#add(Collection) add(Collection)} methods instead, as they ensure a single
* deterministic data type for recipients.
*
* @param aud the JWT {@code aud} value or {@code null} to remove the property from the JSON map.
* @return the {@code Claims} instance for method chaining.
* @deprecated since JJWT_RELEASE_VERSION in favor of the shorter and more modern builder-style named
* {@link #audience(String)}. This method will be removed before the JJWT 1.0 release.
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link #audience()}. This method will be removed before
* the JJWT 1.0 release.
*/
@Deprecated
T setAudience(String aud);
/**
* Sets the JWT <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3"><code>aud</code> (audience)
* Claim</a> as <em>a single String, <b>NOT</b> a String array</em>. This method exists only for producing
* JWTs sent to legacy recipients that are unable to interpret the {@code aud} value as a JSON String Array; it is
* strongly recommended to avoid calling this method whenever possible and favor the
* {@link #audience(String)} or {@link #audience(Collection)} methods instead, as they ensure a single deterministic
* data type for recipients.
*
* @param aud the value to use as the {@code aud} Claim single-String value (and not an array of Strings), or
* {@code null}, empty or whitespace to remove the property from the JSON map.
* @return the instance for method chaining
* @since JJWT_RELEASE_VERSION
* @deprecated This is technically not deprecated because the JWT RFC mandates support for single string values,
* but it is marked as deprecated to discourage its use when possible.
*/
// DO NOT REMOVE EVER. This is a required RFC feature, but marked as deprecated to discourage its use
@Deprecated
T audienceSingle(String aud);
/**
* Adds (appends) the specified {@code aud} value to the {@link #audience(Collection) audience} Claim set
* (JSON Array) unless it is {@code null}, empty, whitespace-only or already exists in the set.
*
* <p>This method may be called multiple times.</p>
*
* @param aud a JWT {@code aud} value to add to the {@link #audience(Collection) audience} Claim set.
* @return the {@code Claims} instance for method chaining.
* @throws IllegalArgumentException if the {@code aud} argument is null or empty.
* @since JJWT_RELEASE_VERSION
*/
T audience(String aud);
/**
* Adds (appends) the specified values to the JWT
* Configures the JWT
* <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3"><code>aud</code></a> (audience) Claim
* set, quietly ignoring any null, empty, whitespace-only, or existing value already in the set.
*
* <p>This method may be called multiple times.</p>
*
* @param aud the values to add to the {@code aud} Claim set (JSON Array)
* @return the instance for method chaining
* @return the {@link AudienceCollection AudienceCollection} to use for {@code aud} configuration.
* @see AudienceCollection AudienceCollection
* @see AudienceCollection#single(String) AudienceCollection.single(String)
* @since JJWT_RELEASE_VERSION
*/
T audience(Collection<String> aud);
AudienceCollection<T> audience();
/**
* Sets the JWT <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4">
@ -246,4 +216,34 @@ public interface ClaimsMutator<T extends ClaimsMutator<T>> {
* @since JJWT_RELEASE_VERSION
*/
T id(String jti);
/**
* A {@code NestedCollection} for setting {@link #audience()} values that also allows overriding the collection
* to be a {@link #single(String) single string value} for legacy JWT recipients if necessary.
*
* @param <P> the type of ClaimsMutator to return for method chaining.
* @see #single(String)
* @since JJWT_RELEASE_VERSION
*/
interface AudienceCollection<P> extends NestedCollection<String, P> {
/**
* Sets the JWT <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3"><code>aud</code> (audience)
* Claim</a> as <em>a single String, <b>NOT</b> a String array</em>. This method exists only for producing
* JWTs sent to legacy recipients that are unable to interpret the {@code aud} value as a JSON String Array;
* it is strongly recommended to avoid calling this method whenever possible and favor the
* {@link #add(Object) add(String)} or {@link #add(Collection)} methods instead, as they ensure a single
* deterministic data type for recipients.
*
* @param aud the value to use as the {@code aud} Claim single-String value (and not an array of Strings), or
* {@code null}, empty or whitespace to remove the property from the JSON map.
* @return the instance for method chaining
* @since JJWT_RELEASE_VERSION
* @deprecated This is technically not deprecated because the JWT RFC mandates support for single string values,
* but it is marked as deprecated to discourage its use when possible.
*/
// DO NOT REMOVE EVER. This is a required RFC feature, but marked as deprecated to discourage its use
@Deprecated
P single(String aud);
}
}

View File

@ -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.
@ -31,9 +29,9 @@ import java.util.Collection;
* {@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

View File

@ -20,6 +20,7 @@ import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoder;
import io.jsonwebtoken.io.Serializer;
import io.jsonwebtoken.lang.Conjunctor;
import io.jsonwebtoken.lang.MapMutator;
import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.InvalidKeyException;
@ -402,9 +403,9 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* String jwt = Jwts.builder()
*
* <b>.claims()
* .subject("Joe")
* .audience("you")
* .issuer("me")
* .subject("Joe")
* .audience().add("you").and()
* .add("customClaim", customValue)
* .add(myClaimsMap)
* // ... etc ...
@ -514,20 +515,6 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
// for better/targeted JavaDoc
JwtBuilder subject(String sub);
/**
* Sets the JWT Claims <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3">
* <code>aud</code></a> (audience) claim. A {@code null} value will remove the property from the Claims.
* This is a convenience wrapper for:
* <blockquote><pre>
* {@link #claims()}.{@link ClaimsMutator#audience(String) audience(aud)}.{@link BuilderClaims#and() and()}</pre></blockquote>
*
* @param aud the JWT {@code aud} value or {@code null} to remove the property from the Claims map.
* @return the builder instance for method chaining.
*/
@Override
// for better/targeted JavaDoc
JwtBuilder audience(String aud);
/**
* Sets the JWT Claims <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4">
* <code>exp</code></a> (expiration) claim. A {@code null} value will remove the property from the Claims.
@ -1052,14 +1039,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
*
* @since JJWT_RELEASE_VERSION
*/
interface BuilderClaims extends MapMutator<String, Object, BuilderClaims>, ClaimsMutator<BuilderClaims> {
/**
* Returns the associated JwtBuilder for continued configuration.
*
* @return the associated JwtBuilder for continued configuration.
*/
JwtBuilder and();
interface BuilderClaims extends MapMutator<String, Object, BuilderClaims>, ClaimsMutator<BuilderClaims>,
Conjunctor<JwtBuilder> {
}
/**
@ -1069,13 +1050,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
*
* @since JJWT_RELEASE_VERSION
*/
interface BuilderHeader extends JweHeaderMutator<BuilderHeader>, X509Builder<BuilderHeader> {
/**
* Returns the associated JwtBuilder for continued configuration.
*
* @return the associated JwtBuilder for continued configuration.
*/
JwtBuilder and();
interface BuilderHeader extends JweHeaderMutator<BuilderHeader>, X509Builder<BuilderHeader>,
Conjunctor<JwtBuilder> {
}
}

View File

@ -30,6 +30,12 @@ package io.jsonwebtoken;
*/
public abstract class JwtHandlerAdapter<T> implements JwtHandler<T> {
/**
* Default constructor, does not initialize any internal state.
*/
public JwtHandlerAdapter() {
}
@Override
public T onContentJwt(Jwt<Header, byte[]> jwt) {
throw new UnsupportedJwtException("Unprotected content JWTs are not supported.");

View File

@ -19,6 +19,8 @@ import io.jsonwebtoken.io.CompressionAlgorithm;
import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Deserializer;
import io.jsonwebtoken.lang.Builder;
import io.jsonwebtoken.lang.Conjunctor;
import io.jsonwebtoken.lang.NestedCollection;
import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.SecureDigestAlgorithm;
@ -29,10 +31,8 @@ import java.security.Key;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Set;
/**
* A builder to construct a {@link JwtParser}. Example usage:
@ -96,25 +96,22 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
*/
JwtParserBuilder unsecuredDecompression();
JwtParserBuilder critical(String crit);
/**
* Specifies the {@link ProtectedHeader} parameter names used in JWT extensions supported by the application. If
* Configures the {@link ProtectedHeader} parameter names used in JWT extensions supported by the application. If
* the parser encounters a Protected JWT that {@link ProtectedHeader#getCritical() requires} extensions, and
* those extensions' header names are not specified via this method, the parser will reject that JWT.
*
* <p><b>Extension Behavior</b></p>
*
* <p>The {@code crit} set argument only identifies header parameter names that are used in extensions supported
* <p>The {@code critical} collection only identifies header parameter names that are used in extensions supported
* by the application. <b>Application developers, <em>not JJWT</em>, MUST perform the associated extension behavior
* using the parsed JWT</b>.</p>
*
* @param crit the header parameter names used in JWT extensions supported by the application.
* @return the builder for method chaining.
* @return the {@link NestedCollection} to use for {@code crit} configuration.
* @see ProtectedHeader#getCritical()
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder critical(Set<String> crit);
NestedCollection<String, JwtParserBuilder> critical();
/**
* Sets the JCA Provider to use during cryptographic signature and key decryption operations, or {@code null} if the
@ -552,117 +549,174 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
JwtParserBuilder setSigningKeyResolver(SigningKeyResolver signingKeyResolver);
/**
* Adds the specified compression algorithms to the parser's total set of supported compression algorithms,
* replacing any existing ones with identical {@link Identifiable#getId() id}s.
* If the parser encounters a JWT {@code zip} header value that equals a compression algorithm's
* {@link Identifiable#getId() id}, that algorithm will be used for decompression.
* Configures the parser's supported {@link AeadAlgorithm}s used to decrypt JWE payloads. If the parser
* encounters a JWE {@link JweHeader#getEncryptionAlgorithm() enc} header value that equals an
* AEAD algorithm's {@link Identifiable#getId() id}, that algorithm will be used to decrypt the JWT
* payload.
*
* <p>There may be only one registered {@code CompressionAlgorithm} per CaSe-SeNsItIvE {@code id}, and the
* {@code algs} collection is added in iteration order; if a duplicate id is found when iterating the {@code algs}
* collection, the later algorithm will evict any existing algorithm with the same {@code id}.</p>
* <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
* configuration, for example:</p>
* <blockquote><pre>
* parserBuilder.enc().add(anAeadAlgorithm).{@link Conjunctor#and() and()} // etc...</pre></blockquote>
*
* <p><b>Standard Algorithms and Overrides</b></p>
*
* <p>The {@link Jwts.ZIP} compression algorithms are supported by default and do not need
* to be added via this method, but beware: <b>any algorithm in the {@code algs} collection with a
* JWA standard {@link Identifiable#getId() id} will replace the JJWT standard algorithm implementation</b>.
* This is to allow application developers to favor their own implementations over JJWT's default implementations
* if necessary (for example, to support legacy or custom behavior).
* <p>All JWA-standard AEAD encryption algorithms in the {@link Jwts.ENC} registry are supported by default and
* do not need to be added. The collection may be useful however for removing some algorithms (for example,
* any algorithms not used by the application, or those not compatible with application security requirements),
* or for adding custom implementations.</p>
*
* @param algs collection of compression algorithms to add to the parser's total set of supported compression
* algorithms, replacing any existing ones with the same exact (CaSe-SeNsItIvE)
* {@link CompressionAlgorithm#getId() id}s.
* @return the builder for method chaining.
* @see Jwts.ZIP
* <p><b>Custom Implementations</b></p>
*
* <p>There may be only one registered {@code AeadAlgorithm} per algorithm {@code id}, and any algorithm
* instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a
* duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. <b>But beware:</b>
*
* <blockquote><b>
* Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will
* replace (override) the JJWT standard algorithm implementation</b>.</blockquote>
*
* <p>This is to allow application developers to favor their
* own implementations over JJWT's default implementations if necessary (for example, to support legacy or
* custom behavior).</p>
*
* @return the {@link NestedCollection} to use to configure the AEAD encryption algorithms available when parsing.
* @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm)
* @see Jwts.ENC
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.2">&quot;enc&quot; (Encryption Algorithm) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-7.1.1">Encryption Algorithm Name (id) requirements</a>
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder addCompressionAlgorithms(Collection<? extends CompressionAlgorithm> algs);
NestedCollection<AeadAlgorithm, JwtParserBuilder> enc();
/**
* Adds the specified AEAD encryption algorithms to the parser's total set of supported encryption algorithms,
* replacing any existing algorithms with the same exact (CaSe-SeNsItIvE) {@link AeadAlgorithm#getId() id}s.
* If the parser encounters a JWT {@code enc} header value that equals an encryption algorithm's
* {@link Identifiable#getId() id}, that algorithm will be used for decryption.
* Configures the parser's supported {@link KeyAlgorithm}s used to obtain a JWE's decryption key. If the
* parser encounters a JWE {@link JweHeader#getAlgorithm()} alg} header value that equals a {@code KeyAlgorithm}'s
* {@link Identifiable#getId() id}, that key algorithm will be used to obtain the JWE's decryption key.
*
* <p>There may be only one registered {@code AeadAlgorithm} per algorithm {@code id}, and the {@code algs}
* collection is added in iteration order; if a duplicate id is found when iterating the {@code algs}
* collection, the later algorithm will evict any existing algorithm with the same {@code id}.</p>
* <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
* configuration, for example:</p>
* <blockquote><pre>
* parserBuilder.key().add(aKeyAlgorithm).{@link Conjunctor#and() and()} // etc...</pre></blockquote>
*
* <p><b>Standard Algorithms and Overrides</b></p>
*
* <p>All JWA standard encryption algorithms in {@link Jwts.ENC} are supported by default and do not need
* to be added via this method, but beware: <b>any algorithm in the {@code algs} collection with a
* JWA standard {@link Identifiable#getId() id} will replace the JJWT standard algorithm implementation</b>.
* This is to allow application developers to favor their own implementations over JJWT's default implementations
* if necessary (for example, to support legacy or custom behavior).
* <p>All JWA-standard key encryption algorithms in the {@link Jwts.KEY} registry are supported by default and
* do not need to be added. The collection may be useful however for removing some algorithms (for example,
* any algorithms not used by the application, or those not compatible with application security requirements),
* or for adding custom implementations.</p>
*
* @param algs collection of AEAD encryption algorithms to add to the parser's total set of supported
* encryption algorithms, replacing any existing algorithms with the same
* {@link AeadAlgorithm#getId() id}s.
* @return the builder for method chaining.
* <p><b>Custom Implementations</b></p>
*
* <p>There may be only one registered {@code KeyAlgorithm} per algorithm {@code id}, and any algorithm
* instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a
* duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. <b>But beware:</b>
*
* <blockquote><b>
* Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will
* replace (override) the JJWT standard algorithm implementation</b>.</blockquote>
*
* <p>This is to allow application developers to favor their
* own implementations over JJWT's default implementations if necessary (for example, to support legacy or
* custom behavior).</p>
*
* @return the {@link NestedCollection} to use to configure the key algorithms available when parsing.
* @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm)
* @see Jwts.KEY
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.1">JWE &quot;alg&quot; (Algorithm) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-7.1.1">Key Algorithm Name (id) requirements</a>
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder addEncryptionAlgorithms(Collection<? extends AeadAlgorithm> algs);
NestedCollection<KeyAlgorithm<?, ?>, JwtParserBuilder> key();
/**
* Adds the specified signature algorithms to the parser's total set of supported signature algorithms,
* replacing any existing algorithms with the same exact (CaSe-SeNsItIvE) {@link Identifiable#getId() id}s.
* If the parser encounters a JWS {@code alg} header value that equals a signature algorithm's
* {@link Identifiable#getId() id}, that algorithm will be used for signature verification.
* Configures the parser's supported
* {@link io.jsonwebtoken.security.SignatureAlgorithm SignatureAlgorithm} and
* {@link io.jsonwebtoken.security.MacAlgorithm MacAlgorithm}s used to verify JWS signatures. If the parser
* encounters a JWS {@link ProtectedHeader#getAlgorithm() alg} header value that equals a signature or MAC
* algorithm's {@link Identifiable#getId() id}, that algorithm will be used to verify the JWS signature.
*
* <p>There may be only one registered {@code SecureDigestAlgorithm} per algorithm {@code id}, and the
* {@code algs} collection is added in iteration order; if a duplicate id is found when iterating the
* {@code algs} argument, the later algorithm will evict any existing algorithm with the same {@code id}.</p>
* <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
* configuration, for example:</p>
* <blockquote><pre>
* parserBuilder.sig().add(aSignatureAlgorithm).{@link Conjunctor#and() and()} // etc...</pre></blockquote>
*
* <p><b>Standard Algorithms and Overrides</b></p>
*
* <p>All JWA standard signature and MAC algorithms in {@link Jwts.SIG} are supported by default and do not need
* to be added via this method, but beware: <b>any algorithm in the {@code algs} collection with a
* JWA standard {@link Identifiable#getId() id} will replace the JJWT standard
* algorithm implementation</b>. This is to allow application developers to favor their own implementations over
* JJWT's default implementations if necessary (for example, to support legacy or custom behavior).
* <p>All JWA-standard signature and MAC algorithms in the {@link Jwts.SIG} registry are supported by default and
* do not need to be added. The collection may be useful however for removing some algorithms (for example,
* any algorithms not used by the application, or those not compatible with application security requirements), or
* for adding custom implementations.</p>
*
* @param algs collection of signature algorithms to add to the parser's total set of supported signature
* algorithms, replacing any existing algorithms with the same exact (CaSe-SeNsItIvE)
* {@link Identifiable#getId() id}s.
* @return the builder for method chaining.
* <p><b>Custom Implementations</b></p>
*
* <p>There may be only one registered {@code SecureDigestAlgorithm} per algorithm {@code id}, and any algorithm
* instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a
* duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. <b>But beware:</b>
*
* <blockquote><b>
* Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will
* replace (override) the JJWT standard algorithm implementation</b>.</blockquote>
*
* <p>This is to allow application developers to favor their
* own implementations over JJWT's default implementations if necessary (for example, to support legacy or
* custom behavior).</p>
*
* @return the {@link NestedCollection} to use to configure the signature and MAC algorithms available when parsing.
* @see JwtBuilder#signWith(Key, SecureDigestAlgorithm)
* @see Jwts.SIG
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.1">JWS &quot;alg&quot; (Algorithm) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-7.1.1">Algorithm Name (id) requirements</a>
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder addSignatureAlgorithms(Collection<? extends SecureDigestAlgorithm<?, ?>> algs);
NestedCollection<SecureDigestAlgorithm<?, ?>, JwtParserBuilder> sig();
/**
* Adds the specified key management algorithms to the parser's total set of supported key algorithms,
* replacing any existing algorithms with the same exact (CaSe-SeNsItIvE) {@link KeyAlgorithm#getId() id}s.
* If the parser encounters a JWE {@code alg} header value that equals a key management algorithm's
* {@link Identifiable#getId() id}, that algorithm will be used to obtain the content decryption key.
* Configures the parser's supported {@link CompressionAlgorithm}s used to decompress JWT payloads. If the parser
* encounters a JWT {@link ProtectedHeader#getCompressionAlgorithm() zip} header value that equals a
* compression algorithm's {@link Identifiable#getId() id}, that algorithm will be used to decompress the JWT
* payload.
*
* <p>There may be only one registered {@code KeyAlgorithm} per algorithm {@code id}, and the {@code algs}
* collection is added in iteration order; if a duplicate id is found when iterating the {@code algs}
* argument, the later algorithm will evict any existing algorithm with the same {@code id}.</p>
* <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
* configuration, for example:</p>
* <blockquote><pre>
* parserBuilder.zip().add(aCompressionAlgorithm).{@link Conjunctor#and() and()} // etc...</pre></blockquote>
*
* <p><b>Standard Algorithms and Overrides</b></p>
*
* <p>All JWA standard key management algorithms in {@link Jwts.KEY} are supported by default and do not need
* to be added via this method, but beware: <b>any algorithm in the {@code algs} collection with a
* JWA standard {@link Identifiable#getId() id} will replace the JJWT standard algorithm implementation</b>.
* This is to allow application developers to favor their own implementations over JJWT's default implementations
* if necessary (for example, to support legacy or custom behavior).
* <p>All JWA-standard compression algorithms in the {@link Jwts.ZIP} registry are supported by default and
* do not need to be added. The collection may be useful however for removing some algorithms (for example,
* any algorithms not used by the application), or for adding custom implementations.</p>
*
* @param algs collection of key management algorithms to add to the parser's total set of supported key
* management algorithms, replacing any existing algorithms with the same exact (CaSe-SeNsItIvE)
* {@link KeyAlgorithm#getId() id}s.
* @return the builder for method chaining.
* <p><b>Custom Implementations</b></p>
*
* <p>There may be only one registered {@code CompressionAlgorithm} per algorithm {@code id}, and any algorithm
* instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a
* duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. <b>But beware:</b>
*
* <blockquote><b>
* Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will
* replace (override) the JJWT standard algorithm implementation</b>.</blockquote>
*
* <p>This is to allow application developers to favor their
* own implementations over JJWT's default implementations if necessary (for example, to support legacy or
* custom behavior).</p>
*
* @return the {@link NestedCollection} to use to configure the compression algorithms available when parsing.
* @see JwtBuilder#compressWith(CompressionAlgorithm)
* @see Jwts.ZIP
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516#section-4.1.3">&quot;zip&quot; (Compression Algorithm) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-7.3.1">Compression Algorithm Name (id) requirements</a>
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder addKeyAlgorithms(Collection<? extends KeyAlgorithm<?, ?>> algs);
NestedCollection<CompressionAlgorithm, JwtParserBuilder> zip();
/**
* <p><b>Deprecated as of JJWT JJWT_RELEASE_VERSION. This method will be removed before the 1.0 release.</b></p>
*
* <p>This method has been deprecated as of JJWT version JJWT_RELEASE_VERSION because it imposed unnecessary
* implementation requirements on application developers when simply adding to a compression algorithm collection
* would suffice. Use the {@link #addCompressionAlgorithms(Collection)} method instead to add
* would suffice. Use the {@link #zip()} method instead to add
* any custom algorithm implementations without needing to also implement a Locator implementation.</p>
*
* <p><b>Previous Documentation</b></p>
@ -682,8 +736,8 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
*
* @param compressionCodecResolver the compression codec resolver used to decompress the JWT body.
* @return the parser builder for method chaining.
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link #addCompressionAlgorithms(Collection)}.
* This method will be removed before the 1.0 release.
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link #zip()}. This method will be removed before the
* 1.0 release.
*/
@Deprecated
JwtParserBuilder setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver);

View File

@ -23,6 +23,7 @@ import io.jsonwebtoken.lang.Assert;
* {@link #locate(JweHeader)} methods for type-specific logic if desired when the encountered header is an
* unprotected JWT, or an integrity-protected JWT (either a JWS or JWE).
*
* @param <T> the type of object to locate
* @since JJWT_RELEASE_VERSION
*/
public abstract class LocatorAdapter<T> implements Locator<T> {

View File

@ -15,11 +15,12 @@
*/
package io.jsonwebtoken;
import io.jsonwebtoken.lang.Conjunctor;
import io.jsonwebtoken.lang.NestedCollection;
import io.jsonwebtoken.security.PublicJwk;
import io.jsonwebtoken.security.X509Mutator;
import java.net.URI;
import java.util.Collection;
/**
* Mutation (modifications) to a {@link ProtectedHeader Header} instance.
@ -30,29 +31,17 @@ import java.util.Collection;
public interface ProtectedHeaderMutator<T extends ProtectedHeaderMutator<T>> extends HeaderMutator<T>, X509Mutator<T> {
/**
* Adds the name of a header parameter used by a JWT or JWA specification extension that <em>MUST</em> be understood
* and supported by the JWT recipient. A {@code null}, empty, whitespace-only or already existing value is ignored.
* Configures names of header parameters used by JWT or JWA specification extensions that <em>MUST</em> be
* understood and supported by the JWT recipient. When finished, use the collection's
* {@link Conjunctor#and() and()} method to return to the header builder, for example:
* <blockquote><pre>
* builder.critical().add("headerName").{@link Conjunctor#and() and()} // etc...</pre></blockquote>
*
* @param crit the name of a header parameter used by a JWT or JWA specification extension that <em>MUST</em> be
* understood and supported by the JWT recipient.
* @return the header for method chaining.
* @return the {@link NestedCollection} to use for {@code crit} configuration.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11">JWS <code>crit</code> (Critical) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.13">JWS <code>crit</code> (Critical) Header Parameter</a>
*/
T critical(String crit);
/**
* Adds names of header parameters used by JWT or JWA specification extensions that <em>MUST</em> be
* understood and supported by the JWT recipient. {@code null}, empty, whitespace-only or already existing
* values are ignored.
*
* @param crit names of header parameters used by JWT or JWA specification extensions that <em>MUST</em> be
* understood and supported by the JWT recipient
* @return the header for method chaining.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11">JWS <code>crit</code> (Critical) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.13">JWS <code>crit</code> (Critical) Header Parameter</a>
*/
T critical(Collection<String> crit);
NestedCollection<String, T> critical();
/**
* Sets the {@code jwk} (JSON Web Key) associated with the JWT. When set for a {@link JwsHeader}, the

View File

@ -36,6 +36,12 @@ public abstract class AbstractDeserializer<T> implements Deserializer<T> {
private static final byte[] EMPTY_BYTES = new byte[0];
/**
* Default constructor, does not initialize any internal state.
*/
protected AbstractDeserializer() {
}
/**
* {@inheritDoc}
*/
@ -67,7 +73,7 @@ public abstract class AbstractDeserializer<T> implements Deserializer<T> {
*
* @param in the input stream to read
* @return the deserialized Java object
* @throws DeserializationException if there is a problem reading the stream or creating the expected Java object
* @throws Exception if there is a problem reading the stream or creating the expected Java object
*/
protected abstract T doDeserialize(InputStream in) throws Exception;
}

View File

@ -29,6 +29,12 @@ import java.io.OutputStream;
*/
public abstract class AbstractSerializer<T> implements Serializer<T> {
/**
* Default constructor, does not initialize any internal state.
*/
protected AbstractSerializer() {
}
/**
* {@inheritDoc}
*/
@ -61,8 +67,8 @@ public abstract class AbstractSerializer<T> implements Serializer<T> {
*
* @param t the object to convert to a byte stream
* @param out the stream to write to
* @throws SerializationException if there is a problem converting the object to a byte stream or writing the
* bytes to the {@code out}put stream.
* @throws Exception if there is a problem converting the object to a byte stream or writing the
* bytes to the {@code out}put stream.
* @since JJWT_RELEASE_VERSION
*/
protected abstract void doSerialize(T t, OutputStream out) throws Exception;

View File

@ -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.
@ -32,10 +33,12 @@ import java.util.Collection;
* <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

View File

@ -18,6 +18,8 @@ package io.jsonwebtoken.io;
/**
* A decoder converts an already-encoded data value to a desired data type.
*
* @param <T> decoding input type
* @param <R> decoding output type
* @since 0.10.0
*/
public interface Decoder<T, R> {

View File

@ -330,6 +330,15 @@ public final class Classes {
}
}
/**
* Returns the {@code instance}'s named (declared) field value.
*
* @param instance the instance with the internal field
* @param fieldName the name of the field to inspect
* @param fieldType the type of field to inspect
* @param <T> field instance value type
* @return the field value
*/
public static <T> T getFieldValue(Object instance, String fieldName, Class<T> fieldType) {
if (instance == null) return null;
try {

View File

@ -0,0 +1,61 @@
/*
* Copyright © 2023 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.lang;
import java.util.Collection;
/**
* Mutation (modifications) to a {@link java.util.Collection} instance while also supporting method chaining. The
* {@link Collection#add(Object)}, {@link Collection#addAll(Collection)}, {@link Collection#remove(Object)}, and
* {@link Collection#clear()} methods do not support method chaining, so this interface enables that behavior.
*
* @param <E> the type of elements in the collection
* @param <M> the mutator subtype, for method chaining
* @since JJWT_RELEASE_VERSION
*/
public interface CollectionMutator<E, M extends CollectionMutator<E, M>> {
/**
* Adds the specified element to the collection.
*
* @param e the element to add.
* @return the mutator/builder for method chaining.
*/
M add(E e);
/**
* Adds the elements to the collection in iteration order.
*
* @param c the collection to add
* @return the mutator/builder for method chaining.
*/
M add(Collection<? extends E> c);
/**
* Removes all elements in the collection.
*
* @return the mutator/builder for method chaining.
*/
M clear();
/**
* Removes the specified element from the collection.
*
* @param e the element to remove.
* @return the mutator/builder for method chaining.
*/
M remove(E e);
}

View File

@ -0,0 +1,33 @@
/*
* Copyright © 2023 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.lang;
/**
* A {@code Conjunctor} supplies a joined object. It is typically used for nested builders to return
* to the source/original builder.
*
* @param <T> the type of joined object to return.
* @since JJWT_RELEASE_VERSION
*/
public interface Conjunctor<T> {
/**
* Returns the joined object.
*
* @return the joined object.
*/
T and();
}

View File

@ -0,0 +1,27 @@
/*
* Copyright © 2023 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.lang;
/**
* A {@link CollectionMutator} that can return access to its parent via the {@link Conjunctor#and() and()} method for
* continued configuration.
*
* @param <E> the type of elements in the collection
* @param <P> the parent to return
* @since JJWT_RELEASE_VERSION
*/
public interface NestedCollection<E, P> extends CollectionMutator<E, NestedCollection<E, P>>, Conjunctor<P> {
}

View File

@ -275,6 +275,12 @@ public final class Strings {
return new String(asciiBytes, StandardCharsets.US_ASCII);
}
/**
* Returns the {@link StandardCharsets#US_ASCII US_ASCII}-encoded bytes of the specified {@code CharSequence}.
*
* @param s the {@code CharSequence} to encode to {@code US_ASCII}.
* @return the {@link StandardCharsets#US_ASCII US_ASCII}-encoded bytes of the specified {@code CharSequence}.
*/
public static byte[] ascii(CharSequence s) {
byte[] bytes = null;
if (s != null) {

View File

@ -16,7 +16,6 @@
package io.jsonwebtoken.security;
import java.security.Key;
import java.util.Collection;
/**
* A {@link JwkBuilder} that builds asymmetric (public or private) JWKs.
@ -69,7 +68,7 @@ public interface AsymmetricJwkBuilder<K extends Key, J extends AsymmetricJwk<K>,
*
* <p>Per
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">JWK RFC 7517, Section 4.3, last paragraph</a>,
* the <code>use (Public Key Use)</code> and {@link #operations(Collection) key_ops (Key Operations)} members
* the <code>use (Public Key Use)</code> and {@link #operations() key_ops (Key Operations)} members
* <em>SHOULD NOT</em> be used together; however, if both are used, the information they convey <em>MUST</em> be
* consistent. Applications should specify which of these members they use, if either is to be used by the
* application.</p>

View File

@ -15,10 +15,11 @@
*/
package io.jsonwebtoken.security;
import io.jsonwebtoken.lang.Conjunctor;
import io.jsonwebtoken.lang.MapMutator;
import io.jsonwebtoken.lang.NestedCollection;
import java.security.Key;
import java.util.Collection;
/**
* A {@link SecurityBuilder} that produces a JWK. A JWK is an immutable set of name/value pairs that represent a
@ -104,83 +105,34 @@ public interface JwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilde
T idFromThumbprint(HashAlgorithm alg);
/**
* Specifies an operation for which the key may be used by adding it to the
* JWK <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">{@code key_ops} (Key Operations)
* Parameter</a> values. This method may be called multiple times.
* Configures the <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">key operations</a> for which
* the key is intended to be used. When finished, use the collection's {@link Conjunctor#and() and()} method to
* return to the JWK builder, for example:
* <blockquote><pre>
* jwkBuilder.operations().add(aKeyOperation).{@link Conjunctor#and() and()} // etc...</pre></blockquote>
*
* <p>The {@code key_ops} (key operations) parameter identifies the operation(s) for which the key is
* intended to be used. The {@code key_ops} parameter is intended for use cases in which public,
* private, or symmetric keys may be present.</p>
*
* <p><b>Security Vulnerability Notice</b></p>
*
* <p>Multiple unrelated key operations <em>SHOULD NOT</em> be specified for a key because of the potential
* vulnerabilities associated with using the same key with multiple algorithms. Thus, the combinations
* {@link Jwks.OP#SIGN sign} with {@link Jwks.OP#VERIFY verify},
* {@link Jwks.OP#ENCRYPT encrypt} with {@link Jwks.OP#DECRYPT decrypt}, and
* {@link Jwks.OP#WRAP_KEY wrapKey} with {@link Jwks.OP#UNWRAP_KEY unwrapKey} are permitted, but other combinations
* <em>SHOULD NOT</em> be used. This is enforced by the builder's key operation
* {@link #operationPolicy(KeyOperationPolicy) policy}.</p>
* <p>The {@code and()} method will throw an {@link IllegalArgumentException} if any of the specified
* {@code KeyOperation}s are not permitted by the JWK's
* {@link #operationPolicy(KeyOperationPolicy) operationPolicy}. See that documentation for more
* information on security vulnerabilities when using the same key with multiple algorithms.</p>
*
* <p><b>Standard {@code KeyOperation}s and Overrides</b></p>
*
* <p>All RFC-standard JWK Key Operations in the {@link Jwks.OP} registry are supported via the builder's default
* operations {@link #operationPolicy(KeyOperationPolicy) policy}, but other (custom) values
* {@link #operationPolicy(KeyOperationPolicy) operationPolicy}, but other (custom) values
* <em>MAY</em> be specified (for example, using a {@link Jwks.OP#builder()}).</p>
*
* <p>If the {@code JwkBuilder} is being used to rebuild or parse an existing JWK however, any custom operations
* should be enabled for the {@code JwkBuilder} by {@link #operationPolicy(KeyOperationPolicy) specifying}
* an operations policy that includes the custom values (e.g. via
* should be enabled by configuring an {@link #operationPolicy(KeyOperationPolicy) operationPolicy}
* that includes the custom values (e.g. via
* {@link Jwks.OP#policy()}.{@link KeyOperationPolicyBuilder#add(KeyOperation) add(customKeyOperation)}).</p>
*
* <p>For best interoperability with other applications however, it is recommended to use only the {@link Jwks.OP}
* constants.</p>
*
* @param operation the value to add to the JWK {@code key_ops} value set
* @return the builder for method chaining.
* @throws IllegalArgumentException if the {@code op} is not permitted by the operations
* {@link #operationPolicy(KeyOperationPolicy) policy}.
* @return the {@link NestedCollection} to use for {@code key_ops} configuration.
* @see Jwks.OP
* @see <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">RFC 7517: key_ops (Key Operations) Parameter</a>
*/
T operation(KeyOperation operation) throws IllegalArgumentException;
/**
* Adds {@code ops} to the JWK <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">{@code key_ops}
* (Key Operations) Parameter</a> values.
*
* <p>The {@code key_ops} (key operations) parameter identifies the operation(s) for which the key is
* intended to be used. The {@code key_ops} parameter is intended for use cases in which public,
* private, or symmetric keys may be present.</p>
*
* <p><b>Security Vulnerability Notice</b></p>
*
* <p>Multiple unrelated key operations <em>SHOULD NOT</em> be specified for a key because of the potential
* vulnerabilities associated with using the same key with multiple algorithms. Thus, the combinations
* {@link Jwks.OP#SIGN sign} with {@link Jwks.OP#VERIFY verify},
* {@link Jwks.OP#ENCRYPT encrypt} with {@link Jwks.OP#DECRYPT decrypt}, and
* {@link Jwks.OP#WRAP_KEY wrapKey} with {@link Jwks.OP#UNWRAP_KEY unwrapKey} are permitted, but other combinations
* <em>SHOULD NOT</em> be used. This is enforced by the builder's default
* operation {@link #operationPolicy(KeyOperationPolicy) policy}.</p>
*
* <p><b>Standard {@code KeyOperation}s and Overrides</b></p>
*
* <p>All RFC-standard JWK Key Operations in the {@link Jwks.OP} registry are supported via the builder's default
* operations {@link #operationPolicy(KeyOperationPolicy) policy}, but other (custom) values
* <em>MAY</em> be specified (for example, using a {@link Jwks.OP#builder()}).</p>
*
* <p>If the {@code JwkBuilder} is being used to rebuild or parse an existing JWK however, any custom operations
* should be enabled for the {@code JwkBuilder} by {@link #operationPolicy(KeyOperationPolicy) specifying}
* an operations policy that includes the custom values (e.g. via
* {@link Jwks.OP#policy()}.{@link KeyOperationPolicyBuilder#add(KeyOperation) add(customKeyOperation)}).</p>
*
* <p>For best interoperability with other applications however, it is recommended to use only the {@link Jwks.OP}
* constants.</p>
*
* @param ops the {@code KeyOperation} values to add to the JWK {@code key_ops} value set
* @return the builder for method chaining.
* @throws IllegalArgumentException if any of the operations are not permitted by the operations
* {@link #operationPolicy(KeyOperationPolicy) policy}.
* @see Jwks.OP
*/
T operations(Collection<KeyOperation> ops) throws IllegalArgumentException;
NestedCollection<KeyOperation, T> operations();
}

View File

@ -17,11 +17,9 @@ package io.jsonwebtoken.security;
import io.jsonwebtoken.lang.Builder;
import java.util.Collection;
/**
* A {@code KeyOperationBuilder} produces {@link KeyOperation} instances that may be added to a JWK's
* {@link JwkBuilder#operations(Collection) key operations} parameter. This is primarily only useful for creating
* {@link JwkBuilder#operations() key operations} parameter. This is primarily only useful for creating
* custom (non-standard) {@code KeyOperation}s for use with a custom {@link KeyOperationPolicy}, as all standard ones
* are available already via the {@link Jwks.OP} registry singleton.
*

View File

@ -33,8 +33,10 @@ public interface KeyOperationPolicied<T extends KeyOperationPolicied<T>> {
* <blockquote><pre>
* Multiple unrelated key operations SHOULD NOT be specified for a key
* because of the potential vulnerabilities associated with using the
* same key with multiple algorithms.
* </pre></blockquote></li>
* same key with multiple algorithms. Thus, the combinations "{@link Jwks.OP#SIGN sign}"
* with "{@link Jwks.OP#VERIFY verify}", "{@link Jwks.OP#ENCRYPT encrypt}" with "{@link Jwks.OP#DECRYPT decrypt}", and "{@link Jwks.OP#WRAP_KEY wrapKey}" with
* "{@link Jwks.OP#UNWRAP_KEY unwrapKey}" are permitted, but other combinations SHOULD NOT be used.</pre></blockquote>
* </li>
* </ul>
*
* <p>If you wish to enable a different policy, perhaps to support additional custom {@code KeyOperation} values,

View File

@ -20,8 +20,8 @@ import java.util.Collection;
/**
* A key operation policy determines which {@link KeyOperation}s may be assigned to a JWK.
*
* @since JJWT_RELEASE_VERSION
* @see JwkBuilder#operationPolicy(KeyOperationPolicy)
* @since JJWT_RELEASE_VERSION
*/
public interface KeyOperationPolicy {
@ -39,5 +39,5 @@ public interface KeyOperationPolicy {
* @param ops the operations to validate
*/
@SuppressWarnings("GrazieInspection")
void validate(Collection<KeyOperation> ops) throws IllegalArgumentException;
void validate(Collection<? extends KeyOperation> ops) throws IllegalArgumentException;
}

View File

@ -17,6 +17,7 @@ package io.jsonwebtoken.security;
import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.lang.Builder;
import io.jsonwebtoken.lang.CollectionMutator;
import java.util.Collection;
@ -32,7 +33,8 @@ import java.util.Collection;
* @see Jwks.OP#builder()
* @since JJWT_RELEASE_VERSION
*/
public interface KeyOperationPolicyBuilder extends Builder<KeyOperationPolicy> {
public interface KeyOperationPolicyBuilder extends CollectionMutator<KeyOperation, KeyOperationPolicyBuilder>,
Builder<KeyOperationPolicy> {
/**
* Allows a JWK to have unrelated {@link KeyOperation}s in its {@code key_ops} parameter values. <b>Be careful
@ -72,8 +74,10 @@ public interface KeyOperationPolicyBuilder extends Builder<KeyOperationPolicy> {
* @see Jwks.OP
* @see Jwks.OP#builder()
* @see JwkBuilder#operationPolicy(KeyOperationPolicy)
* @see JwkBuilder#operations(Collection)
* @see JwkBuilder#operations()
*/
@Override
// for better JavaDoc
KeyOperationPolicyBuilder add(KeyOperation op);
/**
@ -101,8 +105,10 @@ public interface KeyOperationPolicyBuilder extends Builder<KeyOperationPolicy> {
* @see Jwks.OP
* @see Jwks.OP#builder()
* @see JwkBuilder#operationPolicy(KeyOperationPolicy)
* @see JwkBuilder#operations(Collection)
* @see JwkBuilder#operations()
*/
KeyOperationPolicyBuilder add(Collection<KeyOperation> ops);
@Override
// for better JavaDoc
KeyOperationPolicyBuilder add(Collection<? extends KeyOperation> ops);
}

View File

@ -20,6 +20,7 @@ import java.security.Key;
/**
* A request to a cryptographic algorithm requiring a {@link Key}.
*
* @param <T> the type of payload in the request
* @param <K> they type of key used by the algorithm during the request
* @since JJWT_RELEASE_VERSION
*/

View File

@ -25,6 +25,7 @@ import java.security.SecureRandom;
* during instance creation, such as a {@link java.security.Provider} or {@link java.security.SecureRandom}.
*
* @param <T> The type of object that will be created each time {@link #build()} is invoked.
* @param <B> the type of SecurityBuilder returned for method chaining
* @see #provider(Provider)
* @see #random(SecureRandom)
* @since JJWT_RELEASE_VERSION

View File

@ -0,0 +1,34 @@
/*
* Copyright © 2023 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl;
import io.jsonwebtoken.ClaimsMutator;
import io.jsonwebtoken.impl.lang.DefaultNestedCollection;
import java.util.Collection;
/**
* Abstract NestedCollection that requires the AudienceCollection interface to be implemented.
*
* @param <P> type of parent to return
* @since JJWT_RELEASE_VERSION
*/
abstract class AbstractAudienceCollection<P> extends DefaultNestedCollection<String, P>
implements ClaimsMutator.AudienceCollection<P> {
protected AbstractAudienceCollection(P parent, Collection<? extends String> seed) {
super(parent, seed);
}
}

View File

@ -16,19 +16,18 @@
package io.jsonwebtoken.impl;
import io.jsonwebtoken.JweHeaderMutator;
import io.jsonwebtoken.impl.lang.DefaultNestedCollection;
import io.jsonwebtoken.impl.lang.DelegatingMapMutator;
import io.jsonwebtoken.impl.lang.Parameter;
import io.jsonwebtoken.impl.security.X509BuilderSupport;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.NestedCollection;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.PublicJwk;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* @param <T> return type for method chaining
@ -105,29 +104,14 @@ public class DefaultJweHeaderMutator<T extends JweHeaderMutator<T>>
// =============================================================
@Override
public T critical(String crit) {
crit = Strings.clean(crit);
if (Strings.hasText(crit)) {
critical(Collections.setOf(crit));
}
return self();
}
@Override
public T critical(Collection<String> crit) {
if (!Collections.isEmpty(crit)) {
Set<String> existing = Collections.nullSafe(this.DELEGATE.get(DefaultProtectedHeader.CRIT));
Set<String> set = new LinkedHashSet<>(existing.size() + crit.size());
set.addAll(existing);
for (String s : crit) {
s = Strings.clean(s);
if (s != null) {
set.add(s);
}
public NestedCollection<String, T> critical() {
return new DefaultNestedCollection<String, T>(self(), this.DELEGATE.get(DefaultProtectedHeader.CRIT)) {
@Override
public T and() {
put(DefaultProtectedHeader.CRIT, Collections.asSet(getCollection()));
return super.and();
}
put(DefaultProtectedHeader.CRIT, set);
}
return self();
};
}
@Override

View File

@ -74,7 +74,6 @@ import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Map;
@ -435,19 +434,8 @@ public class DefaultJwtBuilder implements JwtBuilder {
}
@Override
public JwtBuilder audienceSingle(String aud) {
//noinspection deprecation
return claims().audienceSingle(aud).and();
}
@Override
public JwtBuilder audience(String aud) {
return claims().audience(aud).and();
}
@Override
public JwtBuilder audience(Collection<String> aud) {
return claims().audience(aud).and();
public AudienceCollection<JwtBuilder> audience() {
return new DelegateAudienceCollection<>((JwtBuilder) this, claims().audience());
}
@Override
@ -586,7 +574,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
this.headerBuilder.add(DefaultHeader.ALGORITHM.getId(), sigAlg.getId());
if (!this.encodePayload) { // b64 extension:
String id = DefaultJwsHeader.B64.getId();
this.headerBuilder.critical(id).add(id, false);
this.headerBuilder.critical().add(id).and().add(id, false);
}
final JwsHeader header = Assert.isInstanceOf(JwsHeader.class, this.headerBuilder.build());
encodeAndWrite("JWS Protected Header", header, jws);

View File

@ -59,6 +59,7 @@ import io.jsonwebtoken.lang.Classes;
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;
@ -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;
@ -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;
@ -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
@ -271,7 +271,7 @@ public class DefaultJwtParser implements JwtParser {
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 + "'";
@ -531,10 +531,10 @@ public class DefaultJwtParser implements JwtParser {
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);
@ -578,7 +578,7 @@ public class DefaultJwtParser implements JwtParser {
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) {

View File

@ -24,15 +24,22 @@ import io.jsonwebtoken.Jwts;
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.Strings;
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;
@ -45,9 +52,7 @@ import java.security.Key;
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;
@ -79,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;
@ -162,7 +168,7 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
@Override
public JwtParserBuilder requireAudience(String audience) {
expectedClaims.audience(audience);
expectedClaims.audience().add(audience).and();
return this;
}
@ -211,20 +217,14 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
}
@Override
public JwtParserBuilder critical(String crit) {
if (Strings.hasText(crit)) {
Set<String> existing = Collections.nullSafe(this.critical);
Set<String> newSet = new LinkedHashSet<>(existing);
newSet.add(crit);
critical(newSet);
}
return this;
}
@Override
public JwtParserBuilder critical(Set<String> crit) {
this.critical = Collections.immutable(new LinkedHashSet<>(Collections.nullSafe(crit)));
return this;
public NestedCollection<String, JwtParserBuilder> critical() {
return new DefaultNestedCollection<String, JwtParserBuilder>(this, this.critical) {
@Override
public JwtParserBuilder and() {
critical = Collections.asSet(getCollection());
return super.and();
}
};
}
@Override
@ -301,31 +301,47 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
}
@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
@ -385,9 +401,9 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
"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);
}
@ -409,10 +425,10 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
decoder,
deserializer,
compressionCodecResolver,
extraZipAlgs,
extraSigAlgs,
extraKeyAlgs,
extraEncAlgs
zipAlgs,
sigAlgs,
keyAlgs,
encAlgs
);
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright © 2023 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl;
import io.jsonwebtoken.ClaimsMutator;
import io.jsonwebtoken.lang.Assert;
import java.util.Collection;
public class DelegateAudienceCollection<P> implements ClaimsMutator.AudienceCollection<P> {
private final ClaimsMutator.AudienceCollection<?> delegate;
private final P parent;
public DelegateAudienceCollection(P parent, ClaimsMutator.AudienceCollection<?> delegate) {
this.parent = Assert.notNull(parent, "Parent cannot be null.");
this.delegate = Assert.notNull(delegate, "Delegate cannot be null.");
}
@Override
public P single(String aud) {
delegate.single(aud);
return parent;
}
@Override
public ClaimsMutator.AudienceCollection<P> add(String s) {
delegate.add(s);
return this;
}
@Override
public ClaimsMutator.AudienceCollection<P> add(Collection<? extends String> c) {
delegate.add(c);
return this;
}
@Override
public ClaimsMutator.AudienceCollection<P> clear() {
delegate.clear();
return this;
}
@Override
public ClaimsMutator.AudienceCollection<P> remove(String s) {
delegate.remove(s);
return this;
}
@Override
public P and() {
delegate.and(); // allow any cleanup/finalization
return parent;
}
}

View File

@ -23,9 +23,7 @@ import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.MapMutator;
import io.jsonwebtoken.lang.Strings;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set;
/**
@ -74,7 +72,8 @@ public class DelegatingClaimsMutator<T extends MapMutator<String, Object, T> & C
@Override
public T setAudience(String aud) {
return audienceSingle(aud);
//noinspection deprecation
return audience().single(aud);
}
private Set<String> getAudience() {
@ -83,51 +82,36 @@ public class DelegatingClaimsMutator<T extends MapMutator<String, Object, T> & C
String existing = get(AUDIENCE_STRING);
remove(AUDIENCE_STRING.getId()); // clear out any canonical/idiomatic values since we're replacing
setDelegate(this.DELEGATE.replace(DefaultClaims.AUDIENCE));
if (Strings.hasText(existing)) {
put(DefaultClaims.AUDIENCE, Collections.setOf(existing)); // replace as Set
}
put(DefaultClaims.AUDIENCE, Collections.setOf(existing)); // replace as Set
}
return get(DefaultClaims.AUDIENCE);
}
@Override
public T audienceSingle(String aud) {
private T audienceSingle(String aud) {
if (!Strings.hasText(aud)) {
return put(DefaultClaims.AUDIENCE, null);
}
// otherwise it's an actual single string, we need to ensure that we can represent it as a single
// string by swapping out the AUDIENCE param if necessary:
if (this.DELEGATE.PARAMS.get(AUDIENCE_STRING.getId()).supports(Collections.emptySet())) { // need to swap:
remove(AUDIENCE_STRING.getId()); //remove any existing value, as conversion will throw an exception
setDelegate(this.DELEGATE.replace(AUDIENCE_STRING));
}
// string by swapping out the AUDIENCE param:
remove(AUDIENCE_STRING.getId()); //remove any existing value, as conversion will throw an exception
setDelegate(this.DELEGATE.replace(AUDIENCE_STRING));
return put(AUDIENCE_STRING, aud);
}
@Override
public T audience(String aud) {
aud = Strings.clean(aud);
if (Strings.hasText(aud)) {
audience(java.util.Collections.singleton(aud));
}
return self();
}
@Override
public T audience(Collection<String> aud) {
if (!Collections.isEmpty(aud)) {
Set<String> existing = Collections.nullSafe(getAudience());
Set<String> set = new LinkedHashSet<>(existing.size() + aud.size());
set.addAll(existing);
for (String s : aud) {
s = Strings.clean(s);
if (s != null) {
set.add(s);
}
public AudienceCollection<T> audience() {
return new AbstractAudienceCollection<T>(self(), getAudience()) {
@Override
public T single(String audience) {
return audienceSingle(audience);
}
put(DefaultClaims.AUDIENCE, set);
}
return self();
@Override
public T and() {
put(DefaultClaims.AUDIENCE, Collections.asSet(getCollection()));
return super.and();
}
};
}
@Override

View File

@ -23,16 +23,11 @@ import io.jsonwebtoken.Locator;
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;
@ -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) {

View File

@ -17,18 +17,19 @@ package io.jsonwebtoken.impl.io;
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));
));
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright © 2023 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.lang;
import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.lang.CollectionMutator;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Objects;
import io.jsonwebtoken.lang.Strings;
import java.util.Collection;
import java.util.LinkedHashSet;
public class DefaultCollectionMutator<E, M extends CollectionMutator<E, M>> implements CollectionMutator<E, M> {
private final Collection<E> collection;
public DefaultCollectionMutator(Collection<? extends E> seed) {
this.collection = new LinkedHashSet<>(Collections.nullSafe(seed));
}
@SuppressWarnings("unchecked")
protected final M self() {
return (M) this;
}
@Override
public M add(E e) {
if (Objects.isEmpty(e)) return self();
if (e instanceof Identifiable && !Strings.hasText(((Identifiable) e).getId())) {
String msg = e.getClass() + " getId() value cannot be null or empty.";
throw new IllegalArgumentException(msg);
}
this.collection.remove(e);
this.collection.add(e);
return self();
}
@Override
public M remove(E e) {
this.collection.remove(e);
return self();
}
@Override
public M add(Collection<? extends E> c) {
for (E element : Collections.nullSafe(c)) {
add(element);
}
return self();
}
@Override
public M clear() {
this.collection.clear();
return self();
}
protected Collection<E> getCollection() {
return Collections.immutable(this.collection);
}
}

View File

@ -16,24 +16,22 @@
package io.jsonwebtoken.impl.lang;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.lang.NestedCollection;
/**
* {@code Registry} implementation that delegates all calls to an internal Registry instance.
*
* @param <K> Registry key type
* @param <V> Registry value type
* @since JJWT_RELEASE_VERSION
*/
public class DelegatingRegistry<K, V> extends DelegatingMap<K, V, Registry<K, V>> implements Registry<K, V> {
import java.util.Collection;
protected DelegatingRegistry(Registry<K, V> registry) {
super(registry);
this.DELEGATE = Assert.notEmpty(registry, "Delegate registry cannot be null or empty.");
public class DefaultNestedCollection<E, P> extends DefaultCollectionMutator<E, NestedCollection<E, P>>
implements NestedCollection<E, P> {
private final P parent;
public DefaultNestedCollection(P parent, Collection<? extends E> seed) {
super(seed);
this.parent = Assert.notNull(parent, "Parent cannot be null.");
}
@Override
public V forKey(K key) throws IllegalArgumentException {
return DELEGATE.forKey(key);
public P and() {
return this.parent;
}
}

View File

@ -84,4 +84,25 @@ public class DefaultRegistry<K, V> extends DelegatingMap<K, V, Map<K, V>> implem
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();
}
}

View File

@ -37,7 +37,18 @@ public class IdRegistry<T extends Identifiable> extends StringRegistry<T> {
}
public IdRegistry(String name, Collection<T> instances) {
this(name, instances, true);
// Each registry requires CaSe-SeNsItIvE keys by default 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(name, instances, true); // <---
}
public IdRegistry(String name, Collection<T> instances, boolean caseSensitive) {

View File

@ -15,12 +15,13 @@
*/
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DefaultNestedCollection;
import io.jsonwebtoken.impl.lang.DelegatingMapMutator;
import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.impl.lang.Parameter;
import io.jsonwebtoken.impl.lang.Parameters;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.NestedCollection;
import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.security.HashAlgorithm;
import io.jsonwebtoken.security.Jwk;
@ -37,7 +38,6 @@ import java.security.Key;
import java.security.Provider;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
abstract class AbstractJwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilder<K, J, T>>
@ -108,18 +108,16 @@ abstract class AbstractJwkBuilder<K extends Key, J extends Jwk<K>, T extends Jwk
}
@Override
public T operation(KeyOperation operation) throws IllegalArgumentException {
return operation != null ? operations(Collections.setOf(operation)) : self();
}
@Override
public T operations(Collection<KeyOperation> ops) {
Set<KeyOperation> set = new LinkedHashSet<>(Collections.nullSafe(ops)); // new ones override existing ones
Set<KeyOperation> existing = Collections.nullSafe(this.DELEGATE.getOperations());
set.addAll(existing);
this.opsPolicy.validate(set);
this.DELEGATE.setOperations(set);
return self();
public NestedCollection<KeyOperation, T> operations() {
return new DefaultNestedCollection<KeyOperation, T>(self(), this.DELEGATE.getOperations()) {
@Override
public T and() {
Collection<? extends KeyOperation> c = getCollection();
opsPolicy.validate(c);
DELEGATE.setOperations(c);
return super.and();
}
};
}
@Override

View File

@ -195,7 +195,7 @@ public class DefaultJwkContext<K extends Key> extends AbstractX509Context<JwkCon
}
@Override
public JwkContext<K> setOperations(Collection<KeyOperation> ops) {
public JwkContext<K> setOperations(Collection<? extends KeyOperation> ops) {
put(AbstractJwk.KEY_OPS, ops);
return this;
}

View File

@ -41,7 +41,7 @@ final class DefaultKeyOperationPolicy implements KeyOperationPolicy {
}
@Override
public void validate(Collection<KeyOperation> ops) {
public void validate(Collection<? extends KeyOperation> ops) {
if (allowUnrelated || Collections.isEmpty(ops)) return;
for (KeyOperation operation : ops) {
for (KeyOperation inner : ops) {

View File

@ -15,24 +15,20 @@
*/
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.impl.lang.DefaultCollectionMutator;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.Jwks;
import io.jsonwebtoken.security.KeyOperation;
import io.jsonwebtoken.security.KeyOperationPolicy;
import io.jsonwebtoken.security.KeyOperationPolicyBuilder;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
public class DefaultKeyOperationPolicyBuilder extends DefaultCollectionMutator<KeyOperation, KeyOperationPolicyBuilder>
implements KeyOperationPolicyBuilder {
public class DefaultKeyOperationPolicyBuilder implements KeyOperationPolicyBuilder {
private final Map<String, KeyOperation> ops;
private boolean unrelated = false;
public DefaultKeyOperationPolicyBuilder() {
this.ops = new LinkedHashMap<>(Jwks.OP.get());
super(Jwks.OP.get().values());
}
@Override
@ -41,28 +37,8 @@ public class DefaultKeyOperationPolicyBuilder implements KeyOperationPolicyBuild
return this;
}
@Override
public KeyOperationPolicyBuilder add(KeyOperation op) {
if (op != null) {
String id = Assert.hasText(op.getId(), "KeyOperation id cannot be null or empty.");
this.ops.remove(id);
this.ops.put(id, op);
}
return this;
}
@Override
public KeyOperationPolicyBuilder add(Collection<KeyOperation> ops) {
if (!Collections.isEmpty(ops)) {
for (KeyOperation op : ops) {
add(op);
}
}
return this;
}
@Override
public KeyOperationPolicy build() {
return new DefaultKeyOperationPolicy(Collections.immutable(this.ops.values()), this.unrelated);
return new DefaultKeyOperationPolicy(Collections.immutable(getCollection()), this.unrelated);
}
}

View File

@ -48,7 +48,7 @@ public interface JwkContext<K extends Key> extends Identifiable, Map<String, Obj
Set<KeyOperation> getOperations();
JwkContext<K> setOperations(Collection<KeyOperation> operations);
JwkContext<K> setOperations(Collection<? extends KeyOperation> operations);
String getAlgorithm();

View File

@ -15,17 +15,16 @@
*/
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.Curve;
import java.security.Key;
public final class StandardCurves extends DelegatingRegistry<String, Curve> {
public final class StandardCurves extends IdRegistry<Curve> {
public StandardCurves() {
super(new IdRegistry<>("Elliptic Curve", Collections.<Curve>of(
super("Elliptic Curve", Collections.<Curve>of(
ECCurve.P256,
ECCurve.P384,
ECCurve.P521,
@ -33,7 +32,7 @@ public final class StandardCurves extends DelegatingRegistry<String, Curve> {
EdwardsCurve.X448,
EdwardsCurve.Ed25519,
EdwardsCurve.Ed448
), false));
));
}
public static Curve findByKey(Key key) {

View File

@ -15,21 +15,22 @@
*/
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.AeadAlgorithm;
@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.ENC
public final class StandardEncryptionAlgorithms extends DelegatingRegistry<String, AeadAlgorithm> {
public final class StandardEncryptionAlgorithms extends IdRegistry<AeadAlgorithm> {
public static final String NAME = "JWE Encryption Algorithm";
public StandardEncryptionAlgorithms() {
super(new IdRegistry<>("JWE Encryption Algorithm", Collections.of(
super(NAME, Collections.of(
(AeadAlgorithm) new HmacAesAeadAlgorithm(128),
new HmacAesAeadAlgorithm(192),
new HmacAesAeadAlgorithm(256),
new GcmAesAeadAlgorithm(128),
new GcmAesAeadAlgorithm(192),
new GcmAesAeadAlgorithm(256)), false));
new GcmAesAeadAlgorithm(256)));
}
}

View File

@ -15,7 +15,6 @@
*/
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.HashAlgorithm;
@ -26,10 +25,10 @@ import io.jsonwebtoken.security.HashAlgorithm;
* @since JJWT_RELEASE_VERSION
*/
@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.security.Jwks.HASH
public class StandardHashAlgorithms extends DelegatingRegistry<String, HashAlgorithm> {
public final class StandardHashAlgorithms extends IdRegistry<HashAlgorithm> {
public StandardHashAlgorithms() {
super(new IdRegistry<>("IANA Hash Algorithm", Collections.<HashAlgorithm>of(
super("IANA Hash Algorithm", Collections.<HashAlgorithm>of(
// We don't include DefaultHashAlgorithm.SHA1 here on purpose because 1) it's not in the JWK IANA
// registry so we don't need to expose it anyway, and 2) we don't want to expose a less-safe algorithm.
// The SHA1 instance only exists in JJWT's codebase to support RFC-required `x5t`
@ -40,6 +39,6 @@ public class StandardHashAlgorithms extends DelegatingRegistry<String, HashAlgor
new DefaultHashAlgorithm("sha3-256"),
new DefaultHashAlgorithm("sha3-384"),
new DefaultHashAlgorithm("sha3-512")
)));
));
}
}

View File

@ -15,7 +15,6 @@
*/
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.KeyAlgorithm;
@ -30,7 +29,9 @@ import java.security.spec.MGF1ParameterSpec;
*
* @since JJWT_RELEASE_VERSION
*/
public final class StandardKeyAlgorithms extends DelegatingRegistry<String, KeyAlgorithm<?, ?>> {
public final class StandardKeyAlgorithms extends IdRegistry<KeyAlgorithm<?, ?>> {
public static final String NAME = "JWE Key Management Algorithm";
private static final String RSA1_5_ID = "RSA1_5";
private static final String RSA1_5_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
@ -42,7 +43,7 @@ public final class StandardKeyAlgorithms extends DelegatingRegistry<String, KeyA
new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
public StandardKeyAlgorithms() {
super(new IdRegistry<>("JWE Key Management Algorithm", Collections.<KeyAlgorithm<?, ?>>of(
super(NAME, Collections.<KeyAlgorithm<?, ?>>of(
new DirectKeyAlgorithm(),
new AesWrapKeyAlgorithm(128),
new AesWrapKeyAlgorithm(192),
@ -60,7 +61,7 @@ public final class StandardKeyAlgorithms extends DelegatingRegistry<String, KeyA
new DefaultRsaKeyAlgorithm(RSA1_5_ID, RSA1_5_TRANSFORMATION),
new DefaultRsaKeyAlgorithm(RSA_OAEP_ID, RSA_OAEP_TRANSFORMATION),
new DefaultRsaKeyAlgorithm(RSA_OAEP_256_ID, RSA_OAEP_256_TRANSFORMATION, RSA_OAEP_256_SPEC)
), false));
));
}
/*

View File

@ -15,15 +15,14 @@
*/
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.KeyOperation;
public class StandardKeyOperations extends DelegatingRegistry<String, KeyOperation> {
public final class StandardKeyOperations extends IdRegistry<KeyOperation> {
public StandardKeyOperations() {
super(new IdRegistry<>("JSON Web Key Operation", Collections.of(
super("JSON Web Key Operation", Collections.of(
DefaultKeyOperation.SIGN,
DefaultKeyOperation.VERIFY,
DefaultKeyOperation.ENCRYPT,
@ -32,6 +31,6 @@ public class StandardKeyOperations extends DelegatingRegistry<String, KeyOperati
DefaultKeyOperation.UNWRAP,
DefaultKeyOperation.DERIVE_KEY,
DefaultKeyOperation.DERIVE_BITS
)));
));
}
}

View File

@ -15,7 +15,6 @@
*/
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.Password;
@ -26,10 +25,12 @@ import java.security.Key;
import java.security.PrivateKey;
@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.SIG
public final class StandardSecureDigestAlgorithms extends DelegatingRegistry<String, SecureDigestAlgorithm<?, ?>> {
public final class StandardSecureDigestAlgorithms extends IdRegistry<SecureDigestAlgorithm<?, ?>> {
public static final String NAME = "JWS Digital Signature or MAC";
public StandardSecureDigestAlgorithms() {
super(new IdRegistry<>("JWS Digital Signature or MAC", Collections.of(
super(NAME, Collections.of(
NoneSignatureAlgorithm.INSTANCE,
DefaultMacAlgorithm.HS256,
DefaultMacAlgorithm.HS384,
@ -44,7 +45,7 @@ public final class StandardSecureDigestAlgorithms extends DelegatingRegistry<Str
EcSignatureAlgorithm.ES384,
EcSignatureAlgorithm.ES512,
EdSignatureAlgorithm.INSTANCE
), false));
));
}
@SuppressWarnings("unchecked")

View File

@ -1131,7 +1131,7 @@ class JwtParserTest {
def one = 'one'
def two = 'two'
def expected = [one, two]
String jwt = Jwts.builder().audience(one).audience(two).compact()
String jwt = Jwts.builder().audience().add(one).add(two).and().compact()
def aud = Jwts.parser().unsecured().requireAudience(one).requireAudience(two).build()
.parseClaimsJwt(jwt).getPayload().getAudience()
assertEquals expected.size(), aud.size()
@ -1142,7 +1142,7 @@ class JwtParserTest {
void testParseAtLeastOneAudiences() {
def one = 'one'
String jwt = Jwts.builder().audience(one).audience('two').compact() // more audiences than required
String jwt = Jwts.builder().audience().add(one).add('two').and().compact() // more audiences than required
def aud = Jwts.parser().unsecured().requireAudience(one) // require only one
.build().parseClaimsJwt(jwt).getPayload().getAudience()

View File

@ -1055,37 +1055,12 @@ class JwtsTest {
def realAlg = Jwts.SIG.HS256 // any alg will do, we're going to wrap it
def key = TestKeys.HS256
def id = realAlg.getId() + 'X' // custom id
def alg = new MacAlgorithm() {
@Override
SecretKeyBuilder key() {
return realAlg.key()
}
@Override
int getKeyBitLength() {
return realAlg.keyBitLength
}
@Override
byte[] digest(SecureRequest<InputStream, SecretKey> request) {
return realAlg.digest(request)
}
@Override
boolean verify(VerifySecureDigestRequest<SecretKey> request) {
return realAlg.verify(request)
}
@Override
String getId() {
return id
}
}
def alg = new TestMacAlgorithm(id: id, delegate: realAlg)
def jws = Jwts.builder().setSubject("joe").signWith(key, alg).compact()
assertEquals 'joe', Jwts.parser()
.addSignatureAlgorithms([alg])
.sig().add(alg).and()
.setSigningKey(key)
.build()
.parseClaimsJws(jws).payload.getSubject()
@ -1129,7 +1104,7 @@ class JwtsTest {
def jwe = Jwts.builder().setSubject("joe").encryptWith(key, encAlg).compact()
assertEquals 'joe', Jwts.parser()
.addEncryptionAlgorithms([encAlg])
.enc().add(encAlg).and()
.decryptWith(key)
.build()
.parseClaimsJwe(jwe).payload.getSubject()
@ -1168,7 +1143,7 @@ class JwtsTest {
try {
Jwts.parser()
.keyLocator(new ConstantKeyLocator(TestKeys.HS256, TestKeys.A128GCM))
.addKeyAlgorithms([badKeyAlg]) // <-- add bad alg here
.key().add(badKeyAlg).and() // <-- add bad alg here
.build()
.parseClaimsJwe(compact)
fail()

View File

@ -73,7 +73,7 @@ class RFC7797Test {
InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(content))
for (def payload : [content, asByteInputStream, asBufferedInputStream]) {
def parser = Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id).build()
def parser = Jwts.parser().verifyWith(key).build()
def jws
if (payload instanceof byte[]) {
jws = parser.parseContentJws(s, (byte[]) payload)
@ -111,7 +111,7 @@ class RFC7797Test {
InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(content))
for (def payload : [content, asByteInputStream, asBufferedInputStream]) {
def parser = Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id).build()
def parser = Jwts.parser().verifyWith(key).build()
def jws
if (payload instanceof byte[]) {
jws = parser.parseClaimsJws(s, (byte[]) payload)
@ -137,7 +137,7 @@ class RFC7797Test {
InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(content))
for (def payload : [content, asByteInputStream, asBufferedInputStream]) {
def parser = Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id).build()
def parser = Jwts.parser().verifyWith(key).build()
def jws
if (payload instanceof byte[]) {
jws = parser.parseContentJws(s, (byte[]) payload)
@ -176,8 +176,7 @@ class RFC7797Test {
String compact = Jwts.builder().content(stream).signWith(key).encodePayload(false).compact()
// signature still verified:
def jwt = Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id)
.build().parseContentJws(compact, data)
def jwt = Jwts.parser().verifyWith(key).build().parseContentJws(compact, data)
assertEquals 'HS256', jwt.header.getAlgorithm()
assertEquals s, Strings.utf8(jwt.getPayload())
}
@ -191,8 +190,7 @@ class RFC7797Test {
String s = Jwts.builder().signWith(key).content(payload).encodePayload(false).compact()
def jws = Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id)
.build().parseClaimsJws(s, payload)
def jws = Jwts.parser().verifyWith(key).build().parseClaimsJws(s, payload)
assertEquals 'me', jws.getPayload().getSubject()
}
@ -232,8 +230,7 @@ class RFC7797Test {
@Test
void testParseContentWithEmptyBytesPayload() {
try {
Jwts.parser().verifyWith(TestKeys.HS256).critical(DefaultJwsHeader.B64.id).build()
.parseContentJws('whatever', Bytes.EMPTY) // <-- empty
Jwts.parser().verifyWith(TestKeys.HS256).build().parseContentJws('whatever', Bytes.EMPTY) // <-- empty
fail()
} catch (IllegalArgumentException expected) {
String msg = 'unencodedPayload argument cannot be null or empty.'
@ -244,8 +241,7 @@ class RFC7797Test {
@Test
void testParseClaimsWithEmptyBytesPayload() {
try {
Jwts.parser().verifyWith(TestKeys.HS256).critical(DefaultJwsHeader.B64.id).build()
.parseClaimsJws('whatever', Bytes.EMPTY) // <-- empty
Jwts.parser().verifyWith(TestKeys.HS256).build().parseClaimsJws('whatever', Bytes.EMPTY) // <-- empty
fail()
} catch (IllegalArgumentException expected) {
String msg = 'unencodedPayload argument cannot be null or empty.'
@ -266,7 +262,7 @@ class RFC7797Test {
// try to parse it as a 'normal' JWS (without supplying the payload):
try {
Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id).build()
Jwts.parser().verifyWith(key).critical().add(DefaultJwsHeader.B64.id).and().build()
.parseContentJws(s) // <-- no payload supplied
fail()
} catch (io.jsonwebtoken.security.SignatureException expected) {
@ -289,7 +285,7 @@ class RFC7797Test {
// try to parse it as a 'normal' JWS (without supplying the payload):
try {
Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id).build()
Jwts.parser().verifyWith(key).critical().add(DefaultJwsHeader.B64.id).and().build()
.parseClaimsJws(s) // <-- no payload supplied
fail()
} catch (io.jsonwebtoken.security.SignatureException expected) {
@ -313,7 +309,7 @@ class RFC7797Test {
// create a non-detached unencoded JWS:
String s = Jwts.builder().signWith(key).content(payload).encodePayload(false).compact()
def jws = Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id).build()
def jws = Jwts.parser().verifyWith(key).critical().add(DefaultJwsHeader.B64.id).and().build()
.parseContentJws(s) // <--- parse normally, without calling parseContentJws(s, unencodedPayload)
assertArrayEquals Strings.utf8(payload), jws.getPayload()
@ -327,7 +323,7 @@ class RFC7797Test {
// create a non-detached unencoded JWS:
String s = Jwts.builder().signWith(key).subject('me').encodePayload(false).compact()
def jws = Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id).build()
def jws = Jwts.parser().verifyWith(key).critical().add(DefaultJwsHeader.B64.id).and().build()
.parseClaimsJws(s) // <--- parse normally, without calling parseClaimsJws(s, unencodedPayload)
assertEquals 'me', jws.getPayload().getSubject()
@ -356,7 +352,7 @@ class RFC7797Test {
InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(compressed))
for (def payload : [compressed, asByteInputStream, asBufferedInputStream]) {
def parser = Jwts.parser().verifyWith(key).critical('b64').build()
def parser = Jwts.parser().verifyWith(key).build()
def jws
if (payload instanceof byte[]) {
jws = parser.parseContentJws(s, (byte[]) payload)
@ -396,7 +392,7 @@ class RFC7797Test {
// now try to parse a compressed unencoded using a signing key resolver:
try {
Jwts.parser().critical(DefaultJwsHeader.B64.id) // enable b64 extension
Jwts.parser()
.setSigningKeyResolver(new SigningKeyResolverAdapter() {
@Override
Key resolveSigningKey(JwsHeader header, byte[] content) {

View File

@ -214,7 +214,7 @@ class AbstractProtectedHeaderTest {
void testCritical() {
Set<String> crits = Collections.setOf('foo', 'bar')
def header = Jwts.header().add('alg', 'HS256').add('foo', 'value1').add('bar', 'value2')
.critical(crits).build() as DefaultProtectedHeader
.critical().add(crits).and().build() as DefaultProtectedHeader
assertEquals crits, header.getCritical()
}

View File

@ -590,11 +590,12 @@ class DefaultJwtBuilderTest {
}
}
@SuppressWarnings('GrDeprecatedAPIUsage')
@Test
void testAudienceSingle() {
def key = TestKeys.HS256
String audienceSingleString = 'test'
def jwt = builder.audienceSingle(audienceSingleString).compact()
def jwt = builder.audience().single(audienceSingleString).compact()
// can't use the parser here to validate because it coerces the string value into an array automatically,
// so we need to check the raw payload:
def encoded = new JwtTokenizer().tokenize(jwt).getPayload()
@ -612,7 +613,8 @@ class DefaultJwtBuilderTest {
void testAudienceSingleMultiple() {
def first = 'first'
def second = 'second'
def jwt = builder.audienceSingle(first).audienceSingle(second).compact()
//noinspection GrDeprecatedAPIUsage
def jwt = builder.audience().single(first).audience().single(second).compact()
// can't use the parser here to validate because it coerces the string value into an array automatically,
// so we need to check the raw payload:
def encoded = new JwtTokenizer().tokenize(jwt).getPayload()
@ -629,8 +631,8 @@ class DefaultJwtBuilderTest {
@Test
void testAudienceSingleThenNull() {
def jwt = builder.id('test')
.audienceSingle('single') // set one
.audienceSingle(null) // remove it entirely
.audience().single('single') // set one
.audience().single(null) // remove it entirely
.compact()
// shouldn't be an audience at all:
@ -646,9 +648,10 @@ class DefaultJwtBuilderTest {
def first = 'first'
def second = 'second'
def expected = [first, second] as Set<String>
def jwt = builder.audienceSingle(first) // sets single value
.audienceSingle(null) // removes entirely
.audience([first, second]) // sets collection
def jwt = builder
.audience().single(first) // sets single value
.audience().single(null) // removes entirely
.audience().add([first, second]).and() // sets collection
.compact()
def aud = Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
@ -659,11 +662,12 @@ class DefaultJwtBuilderTest {
* Test to ensure that if we receive a JWT with a single string value, that the parser coerces it to a String array
* so we don't have to worry about different data types:
*/
@SuppressWarnings('GrDeprecatedAPIUsage')
@Test
void testParseAudienceSingle() {
def key = TestKeys.HS256
String audienceSingleString = 'test'
def jwt = builder.audienceSingle(audienceSingleString).compact()
def jwt = builder.audience().single(audienceSingleString).compact()
assertEquals audienceSingleString, Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload
.getAudience().iterator().next() // a collection, not a single string
@ -672,19 +676,19 @@ class DefaultJwtBuilderTest {
@Test
void testAudience() {
def aud = 'fubar'
def jwt = Jwts.builder().audience(aud).compact()
def jwt = Jwts.builder().audience().add(aud).and().compact()
assertEquals aud, Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience().iterator().next()
}
@Test
void testAudienceNullString() {
def jwt = Jwts.builder().subject('me').audience(null).compact()
def jwt = Jwts.builder().subject('me').audience().add(null).and().compact()
assertNull Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
}
@Test
void testAudienceEmptyString() {
def jwt = Jwts.builder().subject('me').audience(' ').compact()
def jwt = Jwts.builder().subject('me').audience().add(' ').and().compact()
assertNull Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
}
@ -692,7 +696,7 @@ class DefaultJwtBuilderTest {
void testAudienceMultipleTimes() {
def one = 'one'
def two = 'two'
def jwt = Jwts.builder().audience(one).audience(two).compact()
def jwt = Jwts.builder().audience().add(one).add(two).and().compact()
def aud = Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
assertTrue aud.contains(one)
assertTrue aud.contains(two)
@ -701,14 +705,14 @@ class DefaultJwtBuilderTest {
@Test
void testAudienceNullCollection() {
Collection c = null
def jwt = Jwts.builder().subject('me').audience(c).compact()
def jwt = Jwts.builder().subject('me').audience().add(c).and().compact()
assertNull Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
}
@Test
void testAudienceEmptyCollection() {
Collection c = new ArrayList()
def jwt = Jwts.builder().subject('me').audience(c).compact()
def jwt = Jwts.builder().subject('me').audience().add(c).and().compact()
assertNull Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
}
@ -716,7 +720,7 @@ class DefaultJwtBuilderTest {
void testAudienceCollectionWithNullElement() {
Collection c = new ArrayList()
c.add(null)
def jwt = Jwts.builder().subject('me').audience(c).compact()
def jwt = Jwts.builder().subject('me').audience().add(c).and().compact()
assertNull Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
}
@ -728,7 +732,8 @@ class DefaultJwtBuilderTest {
void testAudienceSingleThenAudience() {
def one = 'one'
def two = 'two'
def jwt = Jwts.builder().audienceSingle(one).audience(two).compact()
//noinspection GrDeprecatedAPIUsage
def jwt = Jwts.builder().audience().single(one).audience().add(two).and().compact()
def aud = Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
assertTrue aud.contains(one)
assertTrue aud.contains(two)
@ -743,7 +748,8 @@ class DefaultJwtBuilderTest {
void testAudienceThenAudienceSingle() {
def one = 'one'
def two = 'two'
def jwt = Jwts.builder().audience(one).audienceSingle(two).compact()
//noinspection GrDeprecatedAPIUsage
def jwt = Jwts.builder().audience().add(one).and().audience().single(two).compact()
// can't use the parser here to validate because it coerces the string value into an array automatically,
// so we need to check the raw payload:
@ -763,7 +769,8 @@ class DefaultJwtBuilderTest {
def single = 'one'
def collection = ['two', 'three'] as Set<String>
def expected = ['one', 'two', 'three'] as Set<String>
def jwt = Jwts.builder().audienceSingle(single).audience(collection).compact()
//noinspection GrDeprecatedAPIUsage
def jwt = Jwts.builder().audience().single(single).audience().add(collection).and().compact()
def aud = Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
assertEquals expected.size(), aud.size()
assertTrue aud.contains(single) && aud.containsAll(collection)
@ -779,7 +786,7 @@ class DefaultJwtBuilderTest {
def one = 'one'
def two = 'two'
def three = 'three'
def jwt = Jwts.builder().audience([one, two]).audienceSingle(three).compact()
def jwt = Jwts.builder().audience().add([one, two]).and().audience().single(three).compact()
// can't use the parser here to validate because it coerces the string value into an array automatically,
// so we need to check the raw payload:

View File

@ -325,7 +325,7 @@ class DefaultJwtHeaderBuilderTest {
@Test
void testCritical() {
def crit = ['foo'] as Set<String>
header = jws().add('foo', 'bar').critical(crit).build() as JwsHeader
header = jws().add('foo', 'bar').critical().add(crit).and().build() as JwsHeader
assertTrue header instanceof JwsHeader
assertFalse header instanceof JweHeader
assertEquals crit, header.getCritical()
@ -502,7 +502,7 @@ class DefaultJwtHeaderBuilderTest {
@Test
void testCritSingle() {
def crit = 'test'
def header = jws().add(crit, 'foo').critical(crit).build() as ProtectedHeader
def header = jws().add(crit, 'foo').critical().add(crit).and().build() as ProtectedHeader
def expected = [crit] as Set<String>
assertEquals expected, header.getCritical()
}
@ -511,34 +511,34 @@ class DefaultJwtHeaderBuilderTest {
void testCritSingleNullIgnored() {
def crit = 'test'
def expected = [crit] as Set<String>
def header = jws().add(crit, 'foo').critical(crit).build() as ProtectedHeader
def header = jws().add(crit, 'foo').critical().add(crit).and().build() as ProtectedHeader
assertEquals expected, header.getCritical()
header = builder.critical((String) null).build() as ProtectedHeader // ignored
header = builder.critical().add((String) null).and().build() as ProtectedHeader // ignored
assertEquals expected, header.getCritical() // nothing changed
}
@Test
void testCritNullCollectionIgnored() {
def crit = ['test'] as Set<String>
def header = jws().add('test', 'foo').critical(crit).build() as ProtectedHeader
def header = jws().add('test', 'foo').critical().add(crit).and().build() as ProtectedHeader
assertEquals crit, header.getCritical()
header = builder.critical((Collection) null).build() as ProtectedHeader
header = builder.critical().add((Collection) null).and().build() as ProtectedHeader
assertEquals crit, header.getCritical() // nothing changed
}
@Test
void testCritCollectionWithNullElement() {
def crit = [null] as Set<String>
def header = jws().add('test', 'foo').critical(crit).build() as ProtectedHeader
def header = jws().add('test', 'foo').critical().add(crit).and().build() as ProtectedHeader
assertNull header.getCritical()
}
@Test
void testCritEmptyIgnored() {
def crit = ['test'] as Set<String>
def header = jws().add('test', 'foo').critical(crit).build() as ProtectedHeader
ProtectedHeader header = jws().add('test', 'foo').critical().add(crit).and().build() as ProtectedHeader
assertEquals crit, header.getCritical()
header = builder.critical([] as Set<String>).build()
header = builder.critical().add([] as Set<String>).and().build() as ProtectedHeader
assertEquals crit, header.getCritical() // ignored
}
@ -550,7 +550,7 @@ class DefaultJwtHeaderBuilderTest {
void testCritRemovedForUnprotectedHeader() {
def crit = Collections.setOf('foo', 'bar')
// no JWS or JWE params specified:
def header = builder.add('test', 'value').critical(crit).build()
def header = builder.add('test', 'value').critical().add(crit).and().build()
assertFalse header.containsKey(DefaultProtectedHeader.CRIT.getId())
}
@ -562,7 +562,7 @@ class DefaultJwtHeaderBuilderTest {
void testCritNamesSanitizedWhenHeaderMissingCorrespondingParameter() {
def critGiven = ['foo', 'bar'] as Set<String>
def critExpected = ['foo'] as Set<String>
def header = jws().add('foo', 'fooVal').critical(critGiven).build() as ProtectedHeader
def header = jws().add('foo', 'fooVal').critical().add(critGiven).and().build() as ProtectedHeader
// header didn't set the 'bar' parameter, so 'bar' should not be in the crit values:
assertEquals critExpected, header.getCritical()
}
@ -570,7 +570,7 @@ class DefaultJwtHeaderBuilderTest {
@Test
void testCritNamesRemovedWhenHeaderMissingCorrespondingParameter() {
def critGiven = ['foo'] as Set<String>
def header = jws().critical(critGiven).build() as ProtectedHeader
ProtectedHeader header = jws().critical().add(critGiven).and().build() as ProtectedHeader
// header didn't set the 'foo' parameter, so crit would have been empty, and then removed from the header:
assertNull header.getCritical()
}

View File

@ -17,15 +17,12 @@ package io.jsonwebtoken.impl
import com.fasterxml.jackson.databind.ObjectMapper
import io.jsonwebtoken.*
import io.jsonwebtoken.impl.security.ConstantKeyLocator
import io.jsonwebtoken.impl.security.LocatingKeyResolver
import io.jsonwebtoken.impl.security.TestKeys
import io.jsonwebtoken.impl.security.*
import io.jsonwebtoken.io.*
import io.jsonwebtoken.security.*
import io.jsonwebtoken.security.InvalidKeyException
import org.junit.Before
import org.junit.Test
import javax.crypto.SecretKey
import java.security.Provider
import static org.easymock.EasyMock.*
@ -47,7 +44,7 @@ class DefaultJwtParserBuilderTest {
@Test
void testCriticalEmtpy() {
builder.critical(' ') // shouldn't modify the set
builder.critical().add(' ').and() // shouldn't modify the set
assertTrue builder.@critical.isEmpty()
}
@ -165,26 +162,26 @@ class DefaultJwtParserBuilderTest {
}
}
def parser = builder.setCompressionCodecResolver(resolver).build()
assertSame resolver, parser.zipAlgFn.resolver
assertSame resolver, parser.zipAlgs.resolver
}
@Test
void testAddCompressionAlgorithms() {
def codec = new TestCompressionCodec(id: 'test')
def parser = builder.addCompressionAlgorithms([codec] as Set<CompressionCodec>).build()
def parser = builder.zip().add(codec).and().build()
def header = Jwts.header().add('zip', codec.getId()).build()
assertSame codec, parser.zipAlgFn.locate(header)
assertSame codec, parser.zipAlgs.locate(header)
}
@Test
void testAddCompressionAlgorithmsOverrideDefaults() {
def header = Jwts.header().add('zip', 'DEF').build()
def parser = builder.build()
assertSame Jwts.ZIP.DEF, parser.zipAlgFn.apply(header) // standard implementation default
assertSame Jwts.ZIP.DEF, parser.zipAlgs.apply(header) // standard implementation default
def alg = new TestCompressionCodec(id: 'DEF') // custom impl with standard identifier
parser = builder.addCompressionAlgorithms([alg]).build()
assertSame alg, parser.zipAlgFn.apply(header) // custom one, not standard impl
parser = builder.zip().add(alg).and().build()
assertSame alg, parser.zipAlgs.apply(header) // custom one, not standard impl
}
@Test
@ -192,9 +189,9 @@ class DefaultJwtParserBuilderTest {
def standard = Jwts.header().add('zip', 'DEF').build()
def nonStandard = Jwts.header().add('zip', 'def').build()
def parser = builder.build()
assertSame Jwts.ZIP.DEF, parser.zipAlgFn.apply(standard) // standard implementation default
assertSame Jwts.ZIP.DEF, parser.zipAlgs.apply(standard) // standard implementation default
try {
parser.zipAlgFn.apply(nonStandard)
parser.zipAlgs.apply(nonStandard)
fail()
} catch (UnsupportedJwtException e) {
String msg = "Unrecognized JWT ${DefaultHeader.COMPRESSION_ALGORITHM} header value: def"
@ -207,11 +204,11 @@ class DefaultJwtParserBuilderTest {
final String standardId = Jwts.ENC.A256GCM.getId()
def header = Jwts.header().add('enc', standardId).build()
def parser = builder.build()
assertSame Jwts.ENC.A256GCM, parser.encAlgFn.apply(header) // standard implementation default
assertSame Jwts.ENC.A256GCM, parser.encAlgs.apply(header) // standard implementation default
def custom = new TestAeadAlgorithm(id: standardId) // custom impl with standard identifier
parser = builder.addEncryptionAlgorithms([custom]).build()
assertSame custom, parser.encAlgFn.apply(header) // custom one, not standard impl
parser = builder.enc().add(custom).and().build()
assertSame custom, parser.encAlgs.apply(header) // custom one, not standard impl
}
@Test
@ -220,9 +217,9 @@ class DefaultJwtParserBuilderTest {
def standard = Jwts.header().add('alg', 'foo').add('enc', alg.id).build()
def nonStandard = Jwts.header().add('alg', 'foo').add('enc', alg.id.toLowerCase()).build()
def parser = builder.build()
assertSame alg, parser.encAlgFn.apply(standard) // standard id
assertSame alg, parser.encAlgs.apply(standard) // standard id
try {
parser.encAlgFn.apply(nonStandard) // non-standard id
parser.encAlgs.apply(nonStandard) // non-standard id
fail()
} catch (UnsupportedJwtException e) {
String msg = "Unrecognized JWE ${DefaultJweHeader.ENCRYPTION_ALGORITHM} header value: ${alg.id.toLowerCase()}"
@ -235,11 +232,11 @@ class DefaultJwtParserBuilderTest {
final String standardId = Jwts.KEY.A256GCMKW.id
def header = Jwts.header().add('enc', Jwts.ENC.A256GCM.id).add('alg', standardId).build()
def parser = builder.build()
assertSame Jwts.KEY.A256GCMKW, parser.keyAlgFn.apply(header) // standard implementation default
assertSame Jwts.KEY.A256GCMKW, parser.keyAlgs.apply(header) // standard implementation default
def custom = new TestKeyAlgorithm(id: standardId) // custom impl with standard identifier
parser = builder.addKeyAlgorithms([custom]).build()
assertSame custom, parser.keyAlgFn.apply(header) // custom one, not standard impl
parser = builder.key().add(custom).and().build()
assertSame custom, parser.keyAlgs.apply(header) // custom one, not standard impl
}
@Test
@ -249,9 +246,9 @@ class DefaultJwtParserBuilderTest {
def standard = hb.add('alg', alg.id).build()
def nonStandard = hb.add('alg', alg.id.toLowerCase()).build()
def parser = builder.build()
assertSame alg, parser.keyAlgFn.apply(standard) // standard id
assertSame alg, parser.keyAlgs.apply(standard) // standard id
try {
parser.keyAlgFn.apply(nonStandard) // non-standard id
parser.keyAlgs.apply(nonStandard) // non-standard id
fail()
} catch (UnsupportedJwtException e) {
String msg = "Unrecognized JWE ${DefaultJweHeader.ALGORITHM} header value: ${alg.id.toLowerCase()}"
@ -264,11 +261,11 @@ class DefaultJwtParserBuilderTest {
final String standardId = Jwts.SIG.HS256.id
def header = Jwts.header().add('alg', standardId).build()
def parser = builder.build()
assertSame Jwts.SIG.HS256, parser.sigAlgFn.apply(header) // standard implementation default
assertSame Jwts.SIG.HS256, parser.sigAlgs.apply(header) // standard implementation default
def custom = new TestMacAlgorithm(id: standardId) // custom impl with standard identifier
parser = builder.addSignatureAlgorithms([custom]).build()
assertSame custom, parser.sigAlgFn.apply(header) // custom one, not standard impl
parser = builder.sig().add(custom).and().build()
assertSame custom, parser.sigAlgs.apply(header) // custom one, not standard impl
}
@Test
@ -278,9 +275,9 @@ class DefaultJwtParserBuilderTest {
def standard = hb.build()
def nonStandard = hb.add('alg', alg.id.toLowerCase()).build()
def parser = builder.build()
assertSame alg, parser.sigAlgFn.apply(standard) // standard id
assertSame alg, parser.sigAlgs.apply(standard) // standard id
try {
parser.sigAlgFn.apply(nonStandard) // non-standard id
parser.sigAlgs.apply(nonStandard) // non-standard id
fail()
} catch (UnsupportedJwtException e) {
String msg = "Unrecognized JWS ${DefaultJwsHeader.ALGORITHM} header value: ${alg.id.toLowerCase()}"
@ -298,10 +295,10 @@ class DefaultJwtParserBuilderTest {
}
}
try {
builder.setCompressionCodecResolver(resolver).addCompressionAlgorithms([codec] as Set<CompressionCodec>).build()
builder.setCompressionCodecResolver(resolver).zip().add(codec).and().build()
fail()
} catch (IllegalStateException expected) {
String msg = "Both 'addCompressionAlgorithms' and 'compressionCodecResolver' cannot be specified. Choose either."
String msg = "Both 'zip()' and 'compressionCodecResolver' cannot be configured. Choose either."
assertEquals msg, expected.getMessage()
}
}
@ -410,83 +407,4 @@ class DefaultJwtParserBuilderTest {
}
}
static class TestAeadAlgorithm implements AeadAlgorithm {
String id
int keyBitLength = 256
@Override
String getId() {
return id
}
@Override
void encrypt(AeadRequest request, AeadResult result) throws SecurityException {
}
@Override
void decrypt(DecryptAeadRequest request, OutputStream out) throws SecurityException {
}
@Override
SecretKeyBuilder key() {
return null
}
@Override
int getKeyBitLength() {
return keyBitLength
}
}
static class TestKeyAlgorithm implements KeyAlgorithm {
String id
int keyBitLength = 256
@Override
String getId() {
return id
}
@Override
KeyResult getEncryptionKey(KeyRequest request) throws SecurityException {
return null
}
@Override
SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException {
return null
}
}
static class TestMacAlgorithm implements MacAlgorithm {
String id
@Override
String getId() {
return id
}
@Override
byte[] digest(SecureRequest<InputStream, SecretKey> request) throws SecurityException {
return new byte[0]
}
@Override
boolean verify(VerifySecureDigestRequest<SecretKey> request) throws SecurityException {
return false
}
@Override
SecretKeyBuilder key() {
return null
}
@Override
int getKeyBitLength() {
return 0
}
}
}

View File

@ -245,11 +245,11 @@ class DefaultJwtParserTest {
def key = TestKeys.HS256
def crit = Collections.setOf('whatever')
String jws = Jwts.builder()
.header().critical(crit).add('whatever', 42).and()
.header().critical().add(crit).and().add('whatever', 42).and()
.subject('me')
.signWith(key).compact()
def jwt = Jwts.parser().critical(crit).verifyWith(key).build().parseClaimsJws(jws)
def jwt = Jwts.parser().critical().add(crit).and().verifyWith(key).build().parseClaimsJws(jws)
// no exception thrown, as expected, check the header values:
def parsedCrit = jwt.getHeader().getCritical()

View File

@ -29,7 +29,7 @@ class DefaultJwtTest {
@Test
void testToString() {
String compact = Jwts.builder().header().add('foo', 'bar').and().audience('jsmith').compact()
String compact = Jwts.builder().header().add('foo', 'bar').and().audience().add('jsmith').and().compact()
Jwt jwt = Jwts.parser().unsecured().build().parseClaimsJwt(compact)
assertEquals 'header={foo=bar, alg=none},payload={aud=[jsmith]}', jwt.toString()
}

View File

@ -45,6 +45,7 @@ class DefaultMutableJweHeaderTest {
switch (propName) {
case 'algorithm': header.add('alg', val); break // no setter
case 'compressionAlgorithm': header.add('zip', val); break // no setter
case 'critical': header.critical().add(val).and(); break // no setter
default: header."$propName"(val)
}

View File

@ -0,0 +1,46 @@
/*
* Copyright © 2023 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl
import io.jsonwebtoken.ClaimsMutator
import org.junit.Test
import static org.easymock.EasyMock.*
import static org.junit.Assert.assertSame
class DelegateAudienceCollectionTest {
@Test
void clear() {
ClaimsMutator.AudienceCollection delegate = createMock(ClaimsMutator.AudienceCollection)
expect(delegate.clear()).andReturn(delegate)
replay(delegate)
def c = new DelegateAudienceCollection(this, delegate)
assertSame c, c.clear()
verify delegate
}
@Test
void remove() {
String val = 'hello'
ClaimsMutator.AudienceCollection delegate = createMock(ClaimsMutator.AudienceCollection)
expect(delegate.remove(same(val))).andReturn(delegate)
replay(delegate)
def c = new DelegateAudienceCollection(this, delegate)
assertSame c, c.remove(val)
verify delegate
}
}

View File

@ -40,12 +40,12 @@ class IdLocatorTest {
def a = new StringIdentifiable(value: 'A')
def b = new StringIdentifiable(value: 'B')
registry = new IdRegistry('Foo', [a, b], false)
locator = new IdLocator(TEST_PARAM, registry, Collections.emptyList(), exMsg)
locator = new IdLocator(TEST_PARAM, registry, exMsg)
}
@Test
void unrequiredHeaderValueTest() {
locator = new IdLocator(TEST_PARAM, registry, Collections.emptyList(), null)
locator = new IdLocator(TEST_PARAM, registry, null)
def header = Jwts.header().add('a', 'b').build()
assertNull locator.apply(header)
}

View File

@ -0,0 +1,106 @@
/*
* Copyright © 2023 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.lang
import io.jsonwebtoken.Identifiable
import io.jsonwebtoken.lang.Strings
import org.junit.Before
import org.junit.Test
import static org.junit.Assert.*
/**
* @since JJWT_RELEASE_VERSION
*/
class DefaultCollectionMutatorTest {
private DefaultCollectionMutator m
@Before
void setUp() {
m = new DefaultCollectionMutator(null)
}
@Test
void newInstance() {
def c = m.getCollection()
assertNotNull c
assertTrue c.isEmpty()
}
@Test
void addEmpty() {
m.add(Strings.EMPTY)
assertTrue m.getCollection().isEmpty() // wasn't added
}
@Test
void add() {
def val = 'hello'
m.add(val)
assertEquals Collections.singleton(val), m.getCollection()
}
@Test
void addCollection() {
def vals = ['hello', 'world']
m.add(vals)
assertEquals vals.size(), m.getCollection().size()
assertTrue vals.containsAll(m.getCollection())
assertTrue m.getCollection().containsAll(vals)
def i = m.getCollection().iterator() // order retained
assertEquals vals[0], i.next()
assertEquals vals[1], i.next()
assertFalse i.hasNext()
}
@Test(expected = IllegalArgumentException)
void addIdentifiableWithNullId() {
def e = new Identifiable() {
@Override
String getId() {
return null
}
}
m.add(e)
}
@Test(expected = IllegalArgumentException)
void addIdentifiableWithEmptyId() {
def e = new Identifiable() {
@Override
String getId() {
return ' '
}
}
m.add(e)
}
@Test
void remove() {
m.add('hello').add('world')
m.remove('hello')
assertEquals Collections.singleton('world'), m.getCollection()
}
@Test
void clear() {
m.add('one').add('two').add(['three', 'four'])
assertEquals 4, m.getCollection().size()
m.clear()
assertTrue m.getCollection().isEmpty()
}
}

View File

@ -18,8 +18,7 @@ package io.jsonwebtoken.impl.lang
import org.junit.Before
import org.junit.Test
import static org.junit.Assert.assertEquals
import static org.junit.Assert.fail
import static org.junit.Assert.*
class DefaultRegistryTest {
@ -53,4 +52,30 @@ class DefaultRegistryTest {
def key = 'a'
assertEquals reg.apply(key), reg.get(key)
}
@Test
void testHashCode() {
assertEquals reg.@DELEGATE.hashCode(), reg.hashCode()
}
@Test
void testEqualsIdentity() {
assertEquals reg, reg
}
@Test
void testEqualsValues() {
def another = new DefaultRegistry<>('test', 'id', ['a', 'b', 'c', 'd'], Functions.identity())
assertEquals another, reg
}
@Test
void testNotEquals() {
assertFalse reg.equals(new Object())
}
@Test
void testToString() {
assertEquals reg.@DELEGATE.toString(), reg.toString()
}
}

View File

@ -1,47 +0,0 @@
/*
* Copyright (C) 2023 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.lang
import io.jsonwebtoken.lang.Registry
import org.junit.Before
import org.junit.Test
import static org.junit.Assert.assertEquals
class DelegatingRegistryTest {
Registry<String, String> registry
@Before
void setUp() {
def src = new DefaultRegistry('test', 'id', ['a', 'b', 'c'], Functions.identity())
this.registry = new DelegatingRegistry(src)
}
@Test
void testForKey() {
assertEquals 'a', registry.forKey('a')
assertEquals 'b', registry.forKey('b')
assertEquals 'c', registry.forKey('c')
}
@Test(expected = IllegalArgumentException)
void testForKeyInvalid() {
registry.forKey('invalid') // any key value that doesn't exist
}
}

View File

@ -142,7 +142,7 @@ class AbstractJwkBuilderTest {
def op = Jwks.OP.get().get(s)
def canonical = Collections.setOf(s)
def idiomatic = Collections.setOf(op)
def jwk = builder().operation(op).build()
def jwk = builder().operations().add(op).and().build()
assertEquals idiomatic, jwk.getOperations()
assertEquals canonical, jwk.key_ops
}
@ -153,7 +153,7 @@ class AbstractJwkBuilderTest {
def op = Jwks.OP.builder().id(s).build()
def canonical = Collections.setOf(s)
def idiomatic = Collections.setOf(op)
def jwk = builder().operation(op).build()
def jwk = builder().operations().add(op).and().build()
assertEquals idiomatic, jwk.getOperations()
assertEquals canonical, jwk.key_ops
}
@ -164,7 +164,7 @@ class AbstractJwkBuilderTest {
def op = Jwks.OP.builder().id(s).related('verify').build()
def canonical = Collections.setOf(s)
def idiomatic = Collections.setOf(op)
def jwk = builder().operation(op).build()
def jwk = builder().operations().add(op).and().build()
assertEquals idiomatic, jwk.getOperations()
assertEquals canonical, jwk.key_ops
assertSame op, jwk.getOperations().iterator().next()
@ -172,7 +172,7 @@ class AbstractJwkBuilderTest {
//now assert that the standard VERIFY operation treats this as related since it has the same ID:
canonical = Collections.setOf(s, 'verify')
idiomatic = Collections.setOf(op, Jwks.OP.VERIFY)
jwk = builder().operation(op).operation(Jwks.OP.VERIFY).build() as Jwk
jwk = builder().operations().add(op).add(Jwks.OP.VERIFY).and().build() as Jwk
assertEquals idiomatic, jwk.getOperations()
assertEquals canonical, jwk.key_ops
}
@ -183,7 +183,7 @@ class AbstractJwkBuilderTest {
def b = 'verify'
def canonical = Collections.setOf(a, b)
def idiomatic = Collections.setOf(Jwks.OP.SIGN, Jwks.OP.VERIFY)
def jwk = builder().operations(idiomatic).build()
def jwk = builder().operations().add(idiomatic).and().build()
assertEquals idiomatic, jwk.getOperations()
assertEquals canonical, jwk.key_ops
}
@ -192,7 +192,7 @@ class AbstractJwkBuilderTest {
void testOperationsUnrelated() {
try {
// exception thrown on setter, before calling build:
builder().operations(Collections.setOf(Jwks.OP.SIGN, Jwks.OP.ENCRYPT))
builder().operations().add(Collections.setOf(Jwks.OP.SIGN, Jwks.OP.ENCRYPT)).and()
fail()
} catch (IllegalArgumentException e) {
String msg = 'Unrelated key operations are not allowed. KeyOperation [\'encrypt\' (Encrypt content)] is ' +
@ -240,7 +240,7 @@ class AbstractJwkBuilderTest {
def op = Jwks.OP.builder().id('sign').description('Different Description')
.related(Jwks.OP.VERIFY.id).build()
def builder = builder().operationPolicy(Jwks.OP.policy().add(op).build())
def jwk = builder.operations(Collections.setOf(op, Jwks.OP.VERIFY)).build() as Jwk
def jwk = builder.operations().add(Collections.setOf(op, Jwks.OP.VERIFY)).and().build() as Jwk
assertSame op, jwk.getOperations().find({it.id == 'sign'})
}

View File

@ -124,7 +124,7 @@ class EdSignatureAlgorithmTest {
}
static void testSig(PrivateKey signing, PublicKey verification) {
String jwt = Jwts.builder().issuer('me').audience('you').signWith(signing, alg).compact()
String jwt = Jwts.builder().issuer('me').audience().add('you').and().signWith(signing, alg).compact()
def token = Jwts.parser().verifyWith(verification).build().parseClaimsJws(jwt)
assertEquals([alg: alg.getId()], token.header)
assertEquals 'me', token.getPayload().getIssuer()

View File

@ -147,14 +147,15 @@ class JwksTest {
def op = Jwks.OP.ENCRYPT
def expected = [op] as Set<KeyOperation>
def canonical = [op.getId()] as Set<String>
def jwk = Jwks.builder().key(TestKeys.A128GCM).operation(op).build()
def jwk = Jwks.builder().key(TestKeys.A128GCM).operations().add(op).and().build()
assertEquals expected, jwk.getOperations()
assertEquals canonical, jwk.get(AbstractJwk.KEY_OPS.id)
}
@Test
void testSingleOperationNull() {
def jwk = Jwks.builder().key(TestKeys.A128GCM).operation(null).build() //ignored null
def jwk = Jwks.builder().key(TestKeys.A128GCM).operations().add((KeyOperation) null).and().build()
//ignored null
assertNull jwk.getOperations() //nothing added
assertFalse jwk.containsKey(AbstractJwk.KEY_OPS.id)
}
@ -163,7 +164,7 @@ class JwksTest {
void testSingleOperationAppends() {
def expected = [Jwks.OP.ENCRYPT, Jwks.OP.DECRYPT] as Set<KeyOperation>
def jwk = Jwks.builder().key(TestKeys.A128GCM)
.operation(Jwks.OP.ENCRYPT).operation(Jwks.OP.DECRYPT)
.operations().add(Jwks.OP.ENCRYPT).add(Jwks.OP.DECRYPT).and()
.build()
assertEquals expected, jwk.getOperations()
}
@ -171,20 +172,20 @@ class JwksTest {
@Test
void testOperations() {
def val = [Jwks.OP.SIGN, Jwks.OP.VERIFY] as Set<KeyOperation>
def jwk = Jwks.builder().key(TestKeys.A128GCM).operations(val).build()
def jwk = Jwks.builder().key(TestKeys.A128GCM).operations().add(val).and().build()
assertEquals val, jwk.getOperations()
}
@Test
void testOperationsNull() {
def jwk = Jwks.builder().key(TestKeys.A128GCM).operations(null).build()
def jwk = Jwks.builder().key(TestKeys.A128GCM).operations().add((Collection) null).and().build()
assertNull jwk.getOperations()
assertFalse jwk.containsKey(AbstractJwk.KEY_OPS.id)
}
@Test
void testOperationsEmpty() {
def jwk = Jwks.builder().key(TestKeys.A128GCM).operations(Collections.emptyList()).build()
def jwk = Jwks.builder().key(TestKeys.A128GCM).operations().add(Collections.emptyList()).and().build()
assertNull jwk.getOperations()
}

View File

@ -48,7 +48,7 @@ class SecretJwkFactoryTest {
@Test
void testSignOpSetsKeyHmacSHA256() {
SecretJwk jwk = Jwks.builder().key(TestKeys.HS256).delete('alg').build()
SecretJwk result = Jwks.builder().add(jwk).operations([Jwks.OP.SIGN]).build() as SecretJwk
SecretJwk result = Jwks.builder().add(jwk).operations().add(Jwks.OP.SIGN).and().build() as SecretJwk
assertNull result.getAlgorithm()
assertNull result.get('use')
assertEquals 'HmacSHA256', result.toKey().getAlgorithm()
@ -64,7 +64,7 @@ class SecretJwkFactoryTest {
@Test
void testSignOpSetsKeyHmacSHA384() {
SecretJwk jwk = Jwks.builder().key(TestKeys.HS384).delete('alg').build()
SecretJwk result = Jwks.builder().add(jwk).operations([Jwks.OP.SIGN]).build() as SecretJwk
SecretJwk result = Jwks.builder().add(jwk).operations().add(Jwks.OP.SIGN).and().build() as SecretJwk
assertNull result.getAlgorithm()
assertNull result.get('use')
assertEquals 'HmacSHA384', result.toKey().getAlgorithm()
@ -80,7 +80,7 @@ class SecretJwkFactoryTest {
@Test
void testSignOpSetsKeyHmacSHA512() {
SecretJwk jwk = Jwks.builder().key(TestKeys.HS512).delete('alg').build()
SecretJwk result = Jwks.builder().add(jwk).operations([Jwks.OP.SIGN]).build() as SecretJwk
SecretJwk result = Jwks.builder().add(jwk).operations().add(Jwks.OP.SIGN).and().build() as SecretJwk
assertNull result.getAlgorithm()
assertNull result.get('use')
assertEquals 'HmacSHA512', result.toKey().getAlgorithm()

View File

@ -0,0 +1,47 @@
/*
* Copyright © 2023 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.security
import io.jsonwebtoken.security.*
class TestAeadAlgorithm implements AeadAlgorithm {
String id
int keyBitLength = 256
@Override
String getId() {
return id
}
@Override
void encrypt(AeadRequest request, AeadResult result) throws SecurityException {
}
@Override
void decrypt(DecryptAeadRequest request, OutputStream out) throws SecurityException {
}
@Override
SecretKeyBuilder key() {
return null
}
@Override
int getKeyBitLength() {
return keyBitLength
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright © 2023 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.security
import io.jsonwebtoken.security.*
import javax.crypto.SecretKey
class TestKeyAlgorithm implements KeyAlgorithm {
String id
@Override
String getId() {
return id
}
@Override
KeyResult getEncryptionKey(KeyRequest request) throws SecurityException {
return null
}
@Override
SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException {
return null
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright © 2023 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.security
import io.jsonwebtoken.security.*
import javax.crypto.SecretKey
class TestMacAlgorithm implements MacAlgorithm {
String id
MacAlgorithm delegate
@Override
String getId() {
return id
}
@Override
byte[] digest(SecureRequest<InputStream, SecretKey> request) throws SecurityException {
return delegate.digest(request)
}
@Override
boolean verify(VerifySecureDigestRequest<SecretKey> request) throws SecurityException {
return delegate.verify(request)
}
@Override
SecretKeyBuilder key() {
return delegate.key()
}
@Override
int getKeyBitLength() {
return delegate.getKeyBitLength()
}
}

View File

@ -79,13 +79,6 @@ class EncryptionAlgorithmsTest {
}
}
@Test
void testGetCaseInsensitive() {
for (AeadAlgorithm alg : registry.values()) {
assertSame alg, registry.get(alg.getId().toLowerCase())
}
}
@Test(expected = IllegalArgumentException)
void testForIdWithInvalidId() {
//unlike the 'get' paradigm, 'key' requires the value to exist

View File

@ -23,7 +23,7 @@ import java.security.Key
import static org.junit.Assert.*
/**
* Tests {@link Jwts#KEY} values.
* Tests {@link Jwts.KEY} values.
*
* @since JJWT_RELEASE_VERSION
*/
@ -63,13 +63,6 @@ class KeyAlgorithmsTest {
}
}
@Test
void testForKeyCaseInsensitive() {
for (KeyAlgorithm alg : Jwts.KEY.get().values()) {
assertSame alg, Jwts.KEY.get().forKey(alg.getId().toLowerCase())
}
}
@Test(expected = IllegalArgumentException)
void testForKeyWithInvalidId() {
//unlike the 'get' paradigm, 'key' requires the value to exist
@ -83,13 +76,6 @@ class KeyAlgorithmsTest {
}
}
@Test
void testGetCaseInsensitive() {
for (KeyAlgorithm alg : Jwts.KEY.get().values()) {
assertSame alg, Jwts.KEY.get().get(alg.getId().toLowerCase())
}
}
@Test
void testGetWithInvalidId() {
// 'get' paradigm can return null if not found

View File

@ -45,13 +45,6 @@ class StandardAlgorithmsTest {
}
}
@Test
void testForKeyCaseInsensitive() {
eachRegAlg { reg, alg ->
assertSame alg, reg.forKey(alg.getId().toLowerCase())
}
}
@Test
void testForKeyWithInvalidId() {
//unlike the 'get' paradigm, 'forKey' requires the value to exist
@ -72,13 +65,6 @@ class StandardAlgorithmsTest {
}
}
@Test
void testGetCaseInsensitive() {
eachRegAlg { reg, alg ->
assertSame alg, reg.get(alg.getId().toLowerCase())
}
}
@Test
void testGetWithInvalidId() {
// 'get' paradigm can return null if not found

View File

@ -75,9 +75,8 @@ public class JavaReadmeTest {
.compact();
Jws<byte[]> parsed = Jwts.parser().verifyWith(testKey) // 1
.critical("b64") // 2
.build()
.parseContentJws(jws, content); // 3
.parseContentJws(jws, content); // 2
assertArrayEquals(content, parsed.getPayload());
}
@ -98,7 +97,7 @@ public class JavaReadmeTest {
.compact();
Jws<Claims> parsed = Jwts.parser().verifyWith(testKey) // 1
.critical("b64") // 2
.critical().add("b64").and() // 2
.build()
.parseClaimsJws(jws); // 3
@ -106,7 +105,6 @@ public class JavaReadmeTest {
assert "me".equals(parsed.getPayload().getIssuer());
parsed = Jwts.parser().verifyWith(testKey)
.critical("b64")
.build()
.parseClaimsJws(jws, claimsString.getBytes(StandardCharsets.UTF_8)); // <---
@ -237,7 +235,7 @@ public class JavaReadmeTest {
AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A192GCM, A128GCM, A256CBC-HS512, etc...
// Bob creates the compact JWE with Alice's RSA public key so only she may read it:
String jwe = Jwts.builder().audience("Alice")
String jwe = Jwts.builder().audience().add("Alice").and()
.encryptWith(pair.getPublic(), alg, enc) // <-- Alice's RSA public key
.compact();
@ -285,7 +283,7 @@ public class JavaReadmeTest {
AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A192GCM, A128GCM, A256CBC-HS512, etc...
// Bob creates the compact JWE with Alice's EC public key so only she may read it:
String jwe = Jwts.builder().audience("Alice")
String jwe = Jwts.builder().audience().add("Alice").and()
.encryptWith(pair.getPublic(), alg, enc) // <-- Alice's EC public key
.compact();