Add RFC 9068 Support

Closes gh-13185
This commit is contained in:
Josh Cummings 2025-02-27 11:00:21 -07:00
parent 81e2fd2fe8
commit ab43a660b9
10 changed files with 751 additions and 13 deletions

View File

@ -0,0 +1,172 @@
= OAuth 2.0 Changes
== Validate `typ` Header with `JwtTypeValidator`
`NimbusJwtDecoder` in Spring Security 7 will move `typ` header validation to `JwtTypeValidator` intsead of relying on Nimbus.
This brings it in line with `NimbusJwtDecoder` validating claims instead of relying on Nimbus to validate them.
If you are changing Nimbus's default type validation in a `jwtProcessorCustomizer` method, then you should move that to `JwtTypeValidator` or an implementation of `OAuth2TokenValidator` of your own.
To check if you are prepared for this change, add the default `JwtTypeValidator` to your list of validators, as this will be included by default in 7:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
.validateTypes(false) <1>
// ... your remaining configuration
.build();
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
new JwtIssuerValidator(location), JwtTypeValidator.jwt())); <2>
return jwtDecoder;
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun jwtDecoder(): JwtDecoder {
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
.validateTypes(false) <1>
// ... your remaining configuration
.build()
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
JwtIssuerValidator(location), JwtTypeValidator.jwt())) <2>
return jwtDecoder
}
----
======
<1> - Switch off Nimbus verifying the `typ` (this will be off by default in 7)
<2> - Add the default `typ` validator (this will be included by default in 7)
Note the default value verifies that the `typ` value either be `JWT` or not present, which is the same as the Nimbus default.
It is also aligned with https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.9[RFC 7515] which states that `typ` is optional.
=== I'm Using A `DefaultJOSEObjectTypeVerifier`
If you have something like the following in your configuration:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
.jwtProcessorCustomizer((c) -> c
.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>("JOSE"))
)
.build();
return jwtDecoder;
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun jwtDecoder(): JwtDecoder {
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
.jwtProcessorCustomizer {
it.setJWSTypeVerifier(DefaultJOSEObjectTypeVerifier("JOSE"))
}
.build()
return jwtDecoder
}
----
======
Then change this to:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
.validateTypes(false)
.build();
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
new JwtIssuerValidator(location), new JwtTypeValidator("JOSE")));
return jwtDecoder;
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun jwtDecoder(): JwtDecoder {
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
.validateTypes(false)
.build()
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
JwtIssuerValidator(location), JwtTypeValidator("JOSE")))
return jwtDecoder
}
----
======
To indicate that the `typ` header is optional, use `#setAllowEmpty(true)` (this is the equivalent of including `null` in the list of allowed types in `DefaultJOSEObjectTypeVerifier`).
=== I want to opt-out
If you want to keep doing things the way that you are, then the steps are similar, just in reverse:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
.validateTypes(true) <1>
.jwtProcessorCustomizer((c) -> c
.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>("JOSE"))
)
.build();
jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(
new JwtTimestampValidator(), new JwtIssuerValidator(location))); <2>
return jwtDecoder;
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun jwtDecoder(): JwtDecoder {
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
.validateTypes(true) <1>
.jwtProcessorCustomizer {
it.setJWSTypeVerifier(DefaultJOSEObjectTypeVerifier("JOSE"))
}
.build()
jwtDecoder.setJwtValidator(DelegatingOAuth2TokenValidator(
JwtTimestampValidator(), JwtIssuerValidator(location))) <2>
return jwtDecoder
}
----
======
<1> - leave Nimbus type verification on
<2> - specify the list of validators you need, excluding `JwtTypeValidator`
For additional guidance, please see the xref:servlet/oauth2/resource-server/jwt.adoc#oauth2resourceserver-jwt-validation[JwtDecoder Validators] section in the reference.

View File

@ -936,6 +936,46 @@ fun jwtDecoder(): ReactiveJwtDecoder {
By default, Resource Server configures a clock skew of 60 seconds. By default, Resource Server configures a clock skew of 60 seconds.
==== ====
[[webflux-oauth2resourceserver-validation-rfc9068]]
=== Configuring RFC 9068 Validation
If you need to require tokens that meet https://datatracker.ietf.org/doc/rfc9068/[RFC 9068], you can configure validation in the following way:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
JwtDecoder jwtDecoder() {
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuerUri)
.validateTypes(false).build();
jwtDecoder.setJwtValidator(JwtValidators.createAtJwtValidator()
.audience("https://audience.example.org")
.clientId("client-identifier")
.issuer("https://issuer.example.org").build());
return jwtDecoder;
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun jwtDecoder(): JwtDecoder {
val jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuerUri)
.validateTypes(false).build()
jwtDecoder.setJwtValidator(JwtValidators.createAtJwtValidator()
.audience("https://audience.example.org")
.clientId("client-identifier")
.issuer("https://issuer.example.org").build())
return jwtDecoder
}
----
======
[[webflux-oauth2resourceserver-validation-custom]] [[webflux-oauth2resourceserver-validation-custom]]
==== Configuring a Custom Validator ==== Configuring a Custom Validator

View File

@ -1213,6 +1213,46 @@ fun jwtDecoder(): JwtDecoder {
[NOTE] [NOTE]
By default, Resource Server configures a clock skew of 60 seconds. By default, Resource Server configures a clock skew of 60 seconds.
[[oauth2resourceserver-jwt-validation-rfc9068]]
=== Configuring RFC 9068 Validation
If you need to require tokens that meet https://datatracker.ietf.org/doc/rfc9068/[RFC 9068], you can configure validation in the following way:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuerUri)
.validateTypes(false).build();
jwtDecoder.setJwtValidator(JwtValidators.createAtJwtValidator()
.audience("https://audience.example.org")
.clientId("client-identifier")
.issuer("https://issuer.example.org").build());
return jwtDecoder;
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun jwtDecoder(): JwtDecoder {
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuerUri)
.validateTypes(false).build()
jwtDecoder.setJwtValidator(JwtValidators.createAtJwtValidator()
.audience("https://audience.example.org")
.clientId("client-identifier")
.issuer("https://issuer.example.org").build())
return jwtDecoder
}
----
======
[[oauth2resourceserver-jwt-validation-custom]] [[oauth2resourceserver-jwt-validation-custom]]
=== Configuring a Custom Validator === Configuring a Custom Validator

View File

@ -36,7 +36,7 @@ import org.springframework.util.StringUtils;
*/ */
public final class JwtTypeValidator implements OAuth2TokenValidator<Jwt> { public final class JwtTypeValidator implements OAuth2TokenValidator<Jwt> {
private Collection<String> validTypes; private final Collection<String> validTypes;
private boolean allowEmpty; private boolean allowEmpty;
@ -45,6 +45,10 @@ public final class JwtTypeValidator implements OAuth2TokenValidator<Jwt> {
this.validTypes = new ArrayList<>(validTypes); this.validTypes = new ArrayList<>(validTypes);
} }
public JwtTypeValidator(String... validTypes) {
this(List.of(validTypes));
}
/** /**
* Require that the {@code typ} header be {@code JWT} or absent * Require that the {@code typ} header be {@code JWT} or absent
*/ */

View File

@ -18,10 +18,17 @@ package org.springframework.security.oauth2.jwt;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@ -116,4 +123,152 @@ public final class JwtValidators {
return createDefaultWithValidators(tokenValidators); return createDefaultWithValidators(tokenValidators);
} }
/**
* Return a {@link AtJwtBuilder} for building a validator that conforms to
* <a href="https://datatracker.ietf.org/doc/html/rfc9068">RFC 9068</a>.
* @return the {@link AtJwtBuilder} for configuration
* @since 6.5
*/
public static AtJwtBuilder createAtJwtValidator() {
return new AtJwtBuilder();
}
private static RequireClaimValidator require(String claim) {
return new RequireClaimValidator(claim);
}
/**
* A class for building a validator that conforms to
* <a href="https://datatracker.ietf.org/doc/html/rfc9068">RFC 9068</a>.
*
* <p>
* To comply with this spec, this builder needs you to specify at least the
* {@link #audience}, {@link #issuer}, and {@link #clientId}.
*
* <p>
* While building, the claims are keyed by claim name to allow for simplified lookup
* and replacement in {@link #validators}.
*
* @author Josh Cummings
* @since 6.5
*/
public static final class AtJwtBuilder {
Map<String, OAuth2TokenValidator<Jwt>> validators = new LinkedHashMap<>();
private AtJwtBuilder() {
JwtTimestampValidator timestamps = new JwtTimestampValidator();
this.validators.put(JoseHeaderNames.TYP, new JwtTypeValidator(List.of("at+jwt", "application/at+jwt")));
this.validators.put(JwtClaimNames.EXP, require(JwtClaimNames.EXP).and(timestamps));
this.validators.put(JwtClaimNames.SUB, require(JwtClaimNames.SUB));
this.validators.put(JwtClaimNames.IAT, require(JwtClaimNames.IAT).and(timestamps));
this.validators.put(JwtClaimNames.JTI, require(JwtClaimNames.JTI));
}
/**
* Validate that each token has this <a href=
* "https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1">issuer</a>.
* @param issuer the required issuer
* @return the {@link AtJwtBuilder} for further configuration
*/
public AtJwtBuilder issuer(String issuer) {
return validators((v) -> v.put(JwtClaimNames.ISS, new JwtIssuerValidator(issuer)));
}
/**
* Validate that each token has this <a href=
* "https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3">audience</a>.
* @param audience the required audience
* @return the {@link AtJwtBuilder} for further configuration
*/
public AtJwtBuilder audience(String audience) {
return validators((v) -> v.put(JwtClaimNames.AUD,
require(JwtClaimNames.AUD).satisfies((jwt) -> jwt.getAudience().contains(audience))));
}
/**
* Validate that each token has this <a href=
* "https://datatracker.ietf.org/doc/html/rfc8693#name-client_id-client-identifier">client_id</a>.
* @param clientId the client identifier to use
* @return the {@link AtJwtBuilder} for further configuration
*/
public AtJwtBuilder clientId(String clientId) {
return validators((v) -> v.put("client_id", require("client_id").isEqualTo(clientId)));
}
/**
* Mutate the list of validators by claim name.
*
* <p>
* For example, to add a validator for
* <a href="https://datatracker.ietf.org/doc/html/rfc9068#section-2.2.1">azp</a>
* do: <code>
* builder.validators((v) -> v.put("acr", myValidator()));
* </code>
*
* <p>
* A validator is required for all required RFC 9068 claims.
* @param validators the mutator for the map of validators
* @return the {@link AtJwtBuilder} for further configuration
*/
public AtJwtBuilder validators(Consumer<Map<String, OAuth2TokenValidator<Jwt>>> validators) {
validators.accept(this.validators);
return this;
}
/**
* Build the validator
* @return the RFC 9068 validator
*/
public OAuth2TokenValidator<Jwt> build() {
List.of(JoseHeaderNames.TYP, JwtClaimNames.EXP, JwtClaimNames.SUB, JwtClaimNames.IAT, JwtClaimNames.JTI,
JwtClaimNames.ISS, JwtClaimNames.AUD, "client_id")
.forEach((name) -> Assert.isTrue(this.validators.containsKey(name), name + " must be validated"));
return new DelegatingOAuth2TokenValidator<>(this.validators.values());
}
}
private static final class RequireClaimValidator implements OAuth2TokenValidator<Jwt> {
private final String claimName;
RequireClaimValidator(String claimName) {
this.claimName = claimName;
}
@Override
public OAuth2TokenValidatorResult validate(Jwt token) {
if (token.getClaim(this.claimName) == null) {
return OAuth2TokenValidatorResult
.failure(new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, this.claimName + " must have a value",
"https://datatracker.ietf.org/doc/html/rfc9068#name-data-structure"));
}
return OAuth2TokenValidatorResult.success();
}
OAuth2TokenValidator<Jwt> isEqualTo(String value) {
return and(satisfies((jwt) -> value.equals(jwt.getClaim(this.claimName))));
}
OAuth2TokenValidator<Jwt> satisfies(Predicate<Jwt> predicate) {
return and((jwt) -> {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, this.claimName + " is not valid",
"https://datatracker.ietf.org/doc/html/rfc9068#name-data-structure");
if (predicate.test(jwt)) {
return OAuth2TokenValidatorResult.success();
}
return OAuth2TokenValidatorResult.failure(error);
});
}
OAuth2TokenValidator<Jwt> and(OAuth2TokenValidator<Jwt> that) {
return (jwt) -> {
OAuth2TokenValidatorResult result = validate(jwt);
return (result.hasErrors()) ? result : that.validate(jwt);
};
}
}
} }

View File

@ -279,8 +279,7 @@ public final class NimbusJwtDecoder implements JwtDecoder {
private Function<JWKSource<SecurityContext>, Set<JWSAlgorithm>> defaultAlgorithms = (source) -> Set private Function<JWKSource<SecurityContext>, Set<JWSAlgorithm>> defaultAlgorithms = (source) -> Set
.of(JWSAlgorithm.RS256); .of(JWSAlgorithm.RS256);
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = new DefaultJOSEObjectTypeVerifier<>( private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
JOSEObjectType.JWT, null);
private Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>(); private Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
@ -332,7 +331,8 @@ public final class NimbusJwtDecoder implements JwtDecoder {
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer) * NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer)
* .validateType(false) * .validateType(false)
* .build(); * .build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer); * jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
* new JwtIssuerValidator(issuer), JwtTypeValidator.jwt());
* </code> * </code>
* *
* <p> * <p>
@ -550,8 +550,7 @@ public final class NimbusJwtDecoder implements JwtDecoder {
private JWSAlgorithm jwsAlgorithm; private JWSAlgorithm jwsAlgorithm;
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = new DefaultJOSEObjectTypeVerifier<>( private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
JOSEObjectType.JWT, null);
private RSAPublicKey key; private RSAPublicKey key;
@ -590,7 +589,8 @@ public final class NimbusJwtDecoder implements JwtDecoder {
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer) * NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer)
* .validateType(false) * .validateType(false)
* .build(); * .build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer); * jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
* new JwtIssuerValidator(issuer), JwtTypeValidator.jwt());
* </code> * </code>
* *
* <p> * <p>
@ -686,8 +686,7 @@ public final class NimbusJwtDecoder implements JwtDecoder {
private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256; private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = new DefaultJOSEObjectTypeVerifier<>( private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
JOSEObjectType.JWT, null);
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer; private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
@ -723,7 +722,8 @@ public final class NimbusJwtDecoder implements JwtDecoder {
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer) * NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer)
* .validateType(false) * .validateType(false)
* .build(); * .build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer); * jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
* new JwtIssuerValidator(issuer), JwtTypeValidator.jwt());
* </code> * </code>
* *
* <p> * <p>

View File

@ -32,6 +32,7 @@ import javax.crypto.SecretKey;
import com.nimbusds.jose.Header; import com.nimbusds.jose.Header;
import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWK;
@ -39,6 +40,8 @@ import com.nimbusds.jose.jwk.JWKMatcher;
import com.nimbusds.jose.jwk.JWKSelector; import com.nimbusds.jose.jwk.JWKSelector;
import com.nimbusds.jose.jwk.source.JWKSecurityContextJWKSet; import com.nimbusds.jose.jwk.source.JWKSecurityContextJWKSet;
import com.nimbusds.jose.proc.BadJOSEException; import com.nimbusds.jose.proc.BadJOSEException;
import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier;
import com.nimbusds.jose.proc.JOSEObjectTypeVerifier;
import com.nimbusds.jose.proc.JWKSecurityContext; import com.nimbusds.jose.proc.JWKSecurityContext;
import com.nimbusds.jose.proc.JWSKeySelector; import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector; import com.nimbusds.jose.proc.JWSVerificationKeySelector;
@ -308,6 +311,12 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
*/ */
public static final class JwkSetUriReactiveJwtDecoderBuilder { public static final class JwkSetUriReactiveJwtDecoderBuilder {
private static final JOSEObjectTypeVerifier<JWKSecurityContext> JWT_TYPE_VERIFIER = new DefaultJOSEObjectTypeVerifier<>(
JOSEObjectType.JWT, null);
private static final JOSEObjectTypeVerifier<JWKSecurityContext> NO_TYPE_VERIFIER = (header, context) -> {
};
private static final Duration FOREVER = Duration.ofMillis(Long.MAX_VALUE); private static final Duration FOREVER = Duration.ofMillis(Long.MAX_VALUE);
private Function<WebClient, Mono<String>> jwkSetUri; private Function<WebClient, Mono<String>> jwkSetUri;
@ -315,6 +324,8 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
private Function<ReactiveRemoteJWKSource, Mono<Set<JWSAlgorithm>>> defaultAlgorithms = (source) -> Mono private Function<ReactiveRemoteJWKSource, Mono<Set<JWSAlgorithm>>> defaultAlgorithms = (source) -> Mono
.just(Set.of(JWSAlgorithm.RS256)); .just(Set.of(JWSAlgorithm.RS256));
private JOSEObjectTypeVerifier<JWKSecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
private Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>(); private Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
private WebClient webClient = WebClient.create(); private WebClient webClient = WebClient.create();
@ -349,6 +360,55 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
return this; return this;
} }
/**
* Whether to use Nimbus's typ header verification. This is {@code true} by
* default, however it may change to {@code false} in a future major release.
*
* <p>
* By turning off this feature, {@link NimbusReactiveJwtDecoder} expects
* applications to check the {@code typ} header themselves in order to determine
* what kind of validation is needed
* </p>
*
* <p>
* This is done for you when you use {@link JwtValidators} to construct a
* validator.
*
* <p>
* That means that this: <code>
* NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuer).build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
* </code>
*
* <p>
* Is equivalent to this: <code>
* NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuer)
* .validateType(false)
* .build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
* new JwtIssuerValidator(issuer), JwtTypeValidator.jwt());
* </code>
*
* <p>
* The difference is that by setting this to {@code false}, it allows you to
* provide validation by type, like for {@code at+jwt}:
*
* <code>
* NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuer)
* .validateType(false)
* .build();
* jwtDecoder.setJwtValidator(new MyAtJwtValidator());
* </code>
* @param shouldValidateTypHeader whether Nimbus should validate the typ header or
* not
* @return a {@link JwkSetUriReactiveJwtDecoderBuilder} for further configurations
* @since 6.5
*/
public JwkSetUriReactiveJwtDecoderBuilder validateType(boolean shouldValidateTypHeader) {
this.typeVerifier = shouldValidateTypHeader ? JWT_TYPE_VERIFIER : NO_TYPE_VERIFIER;
return this;
}
/** /**
* Configure the list of * Configure the list of
* <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1" target= * <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1" target=
@ -435,13 +495,14 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
Mono<Tuple2<ConfigurableJWTProcessor<JWKSecurityContext>, Function<JWSAlgorithm, Boolean>>> jwtProcessorMono = jwsKeySelector Mono<Tuple2<ConfigurableJWTProcessor<JWKSecurityContext>, Function<JWSAlgorithm, Boolean>>> jwtProcessorMono = jwsKeySelector
.flatMap((selector) -> { .flatMap((selector) -> {
jwtProcessor.setJWSKeySelector(selector); jwtProcessor.setJWSKeySelector(selector);
jwtProcessor.setJWSTypeVerifier(this.typeVerifier);
return this.jwtProcessorCustomizer.apply(source, jwtProcessor); return this.jwtProcessorCustomizer.apply(source, jwtProcessor);
}) })
.map((processor) -> Tuples.of(processor, getExpectedJwsAlgorithms(processor.getJWSKeySelector()))) .map((processor) -> Tuples.of(processor, getExpectedJwsAlgorithms(processor.getJWSKeySelector())))
.cache((processor) -> FOREVER, (ex) -> Duration.ZERO, () -> Duration.ZERO); .cache((processor) -> FOREVER, (ex) -> Duration.ZERO, () -> Duration.ZERO);
return (jwt) -> { return (jwt) -> {
return jwtProcessorMono.flatMap((tuple) -> { return jwtProcessorMono.flatMap((tuple) -> {
JWTProcessor<JWKSecurityContext> processor = tuple.getT1(); ConfigurableJWTProcessor<JWKSecurityContext> processor = tuple.getT1();
Function<JWSAlgorithm, Boolean> expectedJwsAlgorithms = tuple.getT2(); Function<JWSAlgorithm, Boolean> expectedJwsAlgorithms = tuple.getT2();
JWKSelector selector = createSelector(expectedJwsAlgorithms, jwt.getHeader()); JWKSelector selector = createSelector(expectedJwsAlgorithms, jwt.getHeader());
return source.get(selector) return source.get(selector)
@ -476,10 +537,18 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
*/ */
public static final class PublicKeyReactiveJwtDecoderBuilder { public static final class PublicKeyReactiveJwtDecoderBuilder {
private static final JOSEObjectTypeVerifier<SecurityContext> JWT_TYPE_VERIFIER = new DefaultJOSEObjectTypeVerifier<>(
JOSEObjectType.JWT, null);
private static final JOSEObjectTypeVerifier<SecurityContext> NO_TYPE_VERIFIER = (header, context) -> {
};
private final RSAPublicKey key; private final RSAPublicKey key;
private JWSAlgorithm jwsAlgorithm; private JWSAlgorithm jwsAlgorithm;
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer; private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
private PublicKeyReactiveJwtDecoderBuilder(RSAPublicKey key) { private PublicKeyReactiveJwtDecoderBuilder(RSAPublicKey key) {
@ -505,6 +574,56 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
return this; return this;
} }
/**
* Whether to use Nimbus's typ header verification. This is {@code true} by
* default, however it may change to {@code false} in a future major release.
*
* <p>
* By turning off this feature, {@link NimbusReactiveJwtDecoder} expects
* applications to check the {@code typ} header themselves in order to determine
* what kind of validation is needed
* </p>
*
* <p>
* This is done for you when you use {@link JwtValidators} to construct a
* validator.
*
* <p>
* That means that this: <code>
* NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuer).build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
* </code>
*
* <p>
* Is equivalent to this: <code>
* NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withPublicKey(key)
* .validateType(false)
* .build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
* new JwtIssuerValidator(issuer), JwtTypeValidator.jwt());
* new JwtIssuerValidator(issuer), JwtTypeValidator.jwt());
* </code>
*
* <p>
* The difference is that by setting this to {@code false}, it allows you to
* provide validation by type, like for {@code at+jwt}:
*
* <code>
* NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withPublicKey(key)
* .validateType(false)
* .build();
* jwtDecoder.setJwtValidator(new MyAtJwtValidator());
* </code>
* @param shouldValidateTypHeader whether Nimbus should validate the typ header or
* not
* @return a {@link PublicKeyReactiveJwtDecoderBuilder} for further configurations
* @since 6.5
*/
public PublicKeyReactiveJwtDecoderBuilder validateType(boolean shouldValidateTypHeader) {
this.typeVerifier = shouldValidateTypHeader ? JWT_TYPE_VERIFIER : NO_TYPE_VERIFIER;
return this;
}
/** /**
* Use the given {@link Consumer} to customize the {@link JWTProcessor * Use the given {@link Consumer} to customize the {@link JWTProcessor
* ConfigurableJWTProcessor} before passing it to the build * ConfigurableJWTProcessor} before passing it to the build
@ -535,6 +654,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
JWSKeySelector<SecurityContext> jwsKeySelector = new SingleKeyJWSKeySelector<>(this.jwsAlgorithm, this.key); JWSKeySelector<SecurityContext> jwsKeySelector = new SingleKeyJWSKeySelector<>(this.jwsAlgorithm, this.key);
DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>(); DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
jwtProcessor.setJWSKeySelector(jwsKeySelector); jwtProcessor.setJWSKeySelector(jwsKeySelector);
jwtProcessor.setJWSTypeVerifier(this.typeVerifier);
// Spring Security validates the claim set independent from Nimbus // Spring Security validates the claim set independent from Nimbus
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
}); });
@ -552,10 +672,18 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
*/ */
public static final class SecretKeyReactiveJwtDecoderBuilder { public static final class SecretKeyReactiveJwtDecoderBuilder {
private static final JOSEObjectTypeVerifier<SecurityContext> JWT_TYPE_VERIFIER = new DefaultJOSEObjectTypeVerifier<>(
JOSEObjectType.JWT, null);
private static final JOSEObjectTypeVerifier<SecurityContext> NO_TYPE_VERIFIER = (header, context) -> {
};
private final SecretKey secretKey; private final SecretKey secretKey;
private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256; private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer; private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
private SecretKeyReactiveJwtDecoderBuilder(SecretKey secretKey) { private SecretKeyReactiveJwtDecoderBuilder(SecretKey secretKey) {
@ -582,6 +710,55 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
return this; return this;
} }
/**
* Whether to use Nimbus's typ header verification. This is {@code true} by
* default, however it may change to {@code false} in a future major release.
*
* <p>
* By turning off this feature, {@link NimbusReactiveJwtDecoder} expects
* applications to check the {@code typ} header themselves in order to determine
* what kind of validation is needed
* </p>
*
* <p>
* This is done for you when you use {@link JwtValidators} to construct a
* validator.
*
* <p>
* That means that this: <code>
* NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuer).build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
* </code>
*
* <p>
* Is equivalent to this: <code>
* NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withSecretKey(key)
* .validateType(false)
* .build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
* new JwtIssuerValidator(issuer), JwtTypeValidator.jwt());
* </code>
*
* <p>
* The difference is that by setting this to {@code false}, it allows you to
* provide validation by type, like for {@code at+jwt}:
*
* <code>
* NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withSecretKey(key)
* .validateType(false)
* .build();
* jwtDecoder.setJwtValidator(new MyAtJwtValidator());
* </code>
* @param shouldValidateTypHeader whether Nimbus should validate the typ header or
* not
* @return a {@link PublicKeyReactiveJwtDecoderBuilder} for further configurations
* @since 6.5
*/
public SecretKeyReactiveJwtDecoderBuilder validateType(boolean shouldValidateTypHeader) {
this.typeVerifier = shouldValidateTypHeader ? JWT_TYPE_VERIFIER : NO_TYPE_VERIFIER;
return this;
}
/** /**
* Use the given {@link Consumer} to customize the {@link JWTProcessor * Use the given {@link Consumer} to customize the {@link JWTProcessor
* ConfigurableJWTProcessor} before passing it to the build * ConfigurableJWTProcessor} before passing it to the build
@ -610,6 +787,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
this.secretKey); this.secretKey);
DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>(); DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
jwtProcessor.setJWSKeySelector(jwsKeySelector); jwtProcessor.setJWSKeySelector(jwsKeySelector);
jwtProcessor.setJWSTypeVerifier(this.typeVerifier);
// Spring Security validates the claim set independent from Nimbus // Spring Security validates the claim set independent from Nimbus
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
}); });
@ -626,10 +804,18 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
*/ */
public static final class JwkSourceReactiveJwtDecoderBuilder { public static final class JwkSourceReactiveJwtDecoderBuilder {
private static final JOSEObjectTypeVerifier<JWKSecurityContext> JWT_TYPE_VERIFIER = new DefaultJOSEObjectTypeVerifier<>(
JOSEObjectType.JWT, null);
private static final JOSEObjectTypeVerifier<JWKSecurityContext> NO_TYPE_VERIFIER = (header, context) -> {
};
private final Function<SignedJWT, Flux<JWK>> jwkSource; private final Function<SignedJWT, Flux<JWK>> jwkSource;
private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256; private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256;
private JOSEObjectTypeVerifier<JWKSecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
private Consumer<ConfigurableJWTProcessor<JWKSecurityContext>> jwtProcessorCustomizer; private Consumer<ConfigurableJWTProcessor<JWKSecurityContext>> jwtProcessorCustomizer;
private JwkSourceReactiveJwtDecoderBuilder(Function<SignedJWT, Flux<JWK>> jwkSource) { private JwkSourceReactiveJwtDecoderBuilder(Function<SignedJWT, Flux<JWK>> jwkSource) {
@ -652,6 +838,55 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
return this; return this;
} }
/**
* Whether to use Nimbus's typ header verification. This is {@code true} by
* default, however it may change to {@code false} in a future major release.
*
* <p>
* By turning off this feature, {@link NimbusReactiveJwtDecoder} expects
* applications to check the {@code typ} header themselves in order to determine
* what kind of validation is needed
* </p>
*
* <p>
* This is done for you when you use {@link JwtValidators} to construct a
* validator.
*
* <p>
* That means that this: <code>
* NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withJwkSource(issuer).build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
* </code>
*
* <p>
* Is equivalent to this: <code>
* NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withJwkSource(key)
* .validateType(false)
* .build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
* new JwtIssuerValidator(issuer), JwtTypeValidator.jwt());
* </code>
*
* <p>
* The difference is that by setting this to {@code false}, it allows you to
* provide validation by type, like for {@code at+jwt}:
*
* <code>
* NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withJwkSource(key)
* .validateType(false)
* .build();
* jwtDecoder.setJwtValidator(new MyAtJwtValidator());
* </code>
* @param shouldValidateTypHeader whether Nimbus should validate the typ header or
* not
* @return a {@link JwkSourceReactiveJwtDecoderBuilder} for further configurations
* @since 6.5
*/
public JwkSourceReactiveJwtDecoderBuilder validateType(boolean shouldValidateTypHeader) {
this.typeVerifier = shouldValidateTypHeader ? JWT_TYPE_VERIFIER : NO_TYPE_VERIFIER;
return this;
}
/** /**
* Use the given {@link Consumer} to customize the {@link JWTProcessor * Use the given {@link Consumer} to customize the {@link JWTProcessor
* ConfigurableJWTProcessor} before passing it to the build * ConfigurableJWTProcessor} before passing it to the build
@ -681,6 +916,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
jwkSource); jwkSource);
DefaultJWTProcessor<JWKSecurityContext> jwtProcessor = new DefaultJWTProcessor<>(); DefaultJWTProcessor<JWKSecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
jwtProcessor.setJWSKeySelector(jwsKeySelector); jwtProcessor.setJWSKeySelector(jwsKeySelector);
jwtProcessor.setJWSTypeVerifier(this.typeVerifier);
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
}); });
this.jwtProcessorCustomizer.accept(jwtProcessor); this.jwtProcessorCustomizer.accept(jwtProcessor);

View File

@ -18,12 +18,14 @@ package org.springframework.security.oauth2.jwt;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@ -68,6 +70,40 @@ public class JwtValidatorsTests {
assertThatException().isThrownBy(() -> JwtValidators.createDefaultWithValidators(Collections.emptyList())); assertThatException().isThrownBy(() -> JwtValidators.createDefaultWithValidators(Collections.emptyList()));
} }
@Test
public void createAtJwtWhenIssuerClientIdAudienceThenBuilds() {
Jwt.Builder builder = TestJwts.jwt();
OAuth2TokenValidator<Jwt> validator = JwtValidators.createAtJwtValidator()
.audience("audience")
.clientId("clientId")
.issuer("issuer")
.build();
OAuth2TokenValidatorResult result = validator.validate(builder.build());
assertThat(result.getErrors().toString()).contains("at+jwt")
.contains("aud")
.contains("client_id")
.contains("iss");
result = validator.validate(builder.header(JoseHeaderNames.TYP, "JWT").build());
assertThat(result.getErrors().toString()).contains("at+jwt");
result = validator.validate(builder.header(JoseHeaderNames.TYP, "at+jwt").build());
assertThat(result.getErrors().toString()).doesNotContain("at+jwt");
result = validator.validate(builder.header(JoseHeaderNames.TYP, "application/at+jwt").build());
assertThat(result.getErrors().toString()).doesNotContain("at+jwt");
result = validator.validate(builder.audience(List.of("audience")).build());
assertThat(result.getErrors().toString()).doesNotContain("aud");
result = validator.validate(builder.claim("client_id", "clientId").build());
assertThat(result.getErrors().toString()).doesNotContain("client_id");
result = validator.validate(builder.issuer("issuer").build());
assertThat(result.getErrors().toString()).doesNotContain("iss");
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private boolean containsByType(OAuth2TokenValidator<Jwt> validator, Class<? extends OAuth2TokenValidator<?>> type) { private boolean containsByType(OAuth2TokenValidator<Jwt> validator, Class<? extends OAuth2TokenValidator<?>> type) {
DelegatingOAuth2TokenValidator<Jwt> delegatingOAuth2TokenValidator = (DelegatingOAuth2TokenValidator<Jwt>) validator; DelegatingOAuth2TokenValidator<Jwt> delegatingOAuth2TokenValidator = (DelegatingOAuth2TokenValidator<Jwt>) validator;

View File

@ -849,7 +849,8 @@ public class NimbusJwtDecoderTests {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(TestKeys.DEFAULT_SECRET_KEY) NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(TestKeys.DEFAULT_SECRET_KEY)
.validateType(false) .validateType(false)
.build(); .build();
SignedJWT jwt = signedJwt(TestKeys.DEFAULT_SECRET_KEY, MacAlgorithm.HS256, SignedJWT jwt = signedJwt(TestKeys.DEFAULT_SECRET_KEY,
new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JOSE).build(),
new JWTClaimsSet.Builder().subject("subject").build()); new JWTClaimsSet.Builder().subject("subject").build());
jwtDecoder.decode(jwt.serialize()); jwtDecoder.decode(jwt.serialize());
} }

View File

@ -19,6 +19,8 @@ package org.springframework.security.oauth2.jwt;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey; import java.security.interfaces.RSAPublicKey;
import java.security.spec.EncodedKeySpec; import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
@ -38,6 +40,8 @@ import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner; import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.crypto.MACSigner; import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSecurityContextJWKSet; import com.nimbusds.jose.jwk.source.JWKSecurityContextJWKSet;
@ -658,10 +662,60 @@ public class NimbusReactiveJwtDecoderTests {
assertThat(jwsAlgorithmMapKeySelector.isAllowed(JWSAlgorithm.RS512)).isTrue(); assertThat(jwsAlgorithmMapKeySelector.isAllowed(JWSAlgorithm.RS512)).isTrue();
} }
@Test
public void decodeWhenPublicKeyValidateTypeFalseThenSkipsNimbusTypeValidation() throws Exception {
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY)
.validateType(false)
.build();
RSAPrivateKey privateKey = TestKeys.DEFAULT_PRIVATE_KEY;
SignedJWT jwt = signedJwt(privateKey,
new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JOSE).build(),
new JWTClaimsSet.Builder().subject("subject").build());
jwtDecoder.decode(jwt.serialize()).block();
}
@Test
public void decodeWhenSecretKeyValidateTypeFalseThenSkipsNimbusTypeValidation() throws Exception {
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withSecretKey(TestKeys.DEFAULT_SECRET_KEY)
.validateType(false)
.build();
SignedJWT jwt = signedJwt(TestKeys.DEFAULT_SECRET_KEY,
new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JOSE).build(),
new JWTClaimsSet.Builder().subject("subject").build());
jwtDecoder.decode(jwt.serialize()).block();
}
@Test
public void decodeWhenJwkSourceValidateTypeFalseThenSkipsNimbusTypeValidation() throws Exception {
JWK jwk = new RSAKey.Builder(TestKeys.DEFAULT_PUBLIC_KEY).privateKey(TestKeys.DEFAULT_PRIVATE_KEY)
.algorithm(JWSAlgorithm.RS256)
.build();
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withJwkSource((jwt) -> Flux.just(jwk))
.validateType(false)
.build();
SignedJWT jwt = signedJwt(TestKeys.DEFAULT_PRIVATE_KEY,
new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JOSE).build(),
new JWTClaimsSet.Builder().subject("subject").build());
jwtDecoder.decode(jwt.serialize()).block();
}
private SignedJWT signedJwt(SecretKey secretKey, MacAlgorithm jwsAlgorithm, JWTClaimsSet claimsSet) private SignedJWT signedJwt(SecretKey secretKey, MacAlgorithm jwsAlgorithm, JWTClaimsSet claimsSet)
throws Exception { throws Exception {
SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.parse(jwsAlgorithm.getName())), claimsSet); return signedJwt(secretKey, new JWSHeader(JWSAlgorithm.parse(jwsAlgorithm.getName())), claimsSet);
}
private SignedJWT signedJwt(SecretKey secretKey, JWSHeader header, JWTClaimsSet claimsSet) throws Exception {
JWSSigner signer = new MACSigner(secretKey); JWSSigner signer = new MACSigner(secretKey);
return signedJwt(signer, header, claimsSet);
}
private SignedJWT signedJwt(PrivateKey privateKey, JWSHeader header, JWTClaimsSet claimsSet) throws Exception {
JWSSigner signer = new RSASSASigner(privateKey);
return signedJwt(signer, header, claimsSet);
}
private SignedJWT signedJwt(JWSSigner signer, JWSHeader header, JWTClaimsSet claimsSet) throws Exception {
SignedJWT signedJWT = new SignedJWT(header, claimsSet);
signedJWT.sign(signer); signedJWT.sign(signer);
return signedJWT; return signedJWT;
} }