mirror of https://github.com/jwtk/jjwt.git
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:
parent
20b2fa9d50
commit
854bb8944c
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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> {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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">"enc" (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 "alg" (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 "alg" (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">"zip" (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);
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,7 +67,7 @@ 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
|
||||
* @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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
public NestedCollection<String, T> critical() {
|
||||
return new DefaultNestedCollection<String, T>(self(), this.DELEGATE.get(DefaultProtectedHeader.CRIT)) {
|
||||
@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 T and() {
|
||||
put(DefaultProtectedHeader.CRIT, Collections.asSet(getCollection()));
|
||||
return super.and();
|
||||
}
|
||||
}
|
||||
put(DefaultProtectedHeader.CRIT, set);
|
||||
}
|
||||
return self();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
public NestedCollection<String, JwtParserBuilder> critical() {
|
||||
return new DefaultNestedCollection<String, JwtParserBuilder>(this, this.critical) {
|
||||
@Override
|
||||
public JwtParserBuilder critical(Set<String> crit) {
|
||||
this.critical = Collections.immutable(new LinkedHashSet<>(Collections.nullSafe(crit)));
|
||||
return this;
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
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:
|
||||
// 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();
|
||||
public AudienceCollection<T> audience() {
|
||||
return new AbstractAudienceCollection<T>(self(), getAudience()) {
|
||||
@Override
|
||||
public T single(String audience) {
|
||||
return audienceSingle(audience);
|
||||
}
|
||||
|
||||
@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 T and() {
|
||||
put(DefaultClaims.AUDIENCE, Collections.asSet(getCollection()));
|
||||
return super.and();
|
||||
}
|
||||
}
|
||||
put(DefaultClaims.AUDIENCE, set);
|
||||
}
|
||||
return self();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
public NestedCollection<KeyOperation, T> operations() {
|
||||
return new DefaultNestedCollection<KeyOperation, T>(self(), this.DELEGATE.getOperations()) {
|
||||
@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 T and() {
|
||||
Collection<? extends KeyOperation> c = getCollection();
|
||||
opsPolicy.validate(c);
|
||||
DELEGATE.setOperations(c);
|
||||
return super.and();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
)));
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
));
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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
|
||||
)));
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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'})
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in New Issue