mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-22 12:02:14 +00:00
Add RFC 9068 Support
Closes gh-13185
This commit is contained in:
parent
81e2fd2fe8
commit
ab43a660b9
172
docs/modules/ROOT/pages/migration/oauth2.adoc
Normal file
172
docs/modules/ROOT/pages/migration/oauth2.adoc
Normal 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.
|
@ -936,6 +936,46 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
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]]
|
||||
==== Configuring a Custom Validator
|
||||
|
||||
|
@ -1213,6 +1213,46 @@ fun jwtDecoder(): JwtDecoder {
|
||||
[NOTE]
|
||||
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]]
|
||||
=== Configuring a Custom Validator
|
||||
|
||||
|
@ -36,7 +36,7 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public final class JwtTypeValidator implements OAuth2TokenValidator<Jwt> {
|
||||
|
||||
private Collection<String> validTypes;
|
||||
private final Collection<String> validTypes;
|
||||
|
||||
private boolean allowEmpty;
|
||||
|
||||
@ -45,6 +45,10 @@ public final class JwtTypeValidator implements OAuth2TokenValidator<Jwt> {
|
||||
this.validTypes = new ArrayList<>(validTypes);
|
||||
}
|
||||
|
||||
public JwtTypeValidator(String... validTypes) {
|
||||
this(List.of(validTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Require that the {@code typ} header be {@code JWT} or absent
|
||||
*/
|
||||
|
@ -18,10 +18,17 @@ package org.springframework.security.oauth2.jwt;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
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.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@ -116,4 +123,152 @@ public final class JwtValidators {
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -279,8 +279,7 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
private Function<JWKSource<SecurityContext>, Set<JWSAlgorithm>> defaultAlgorithms = (source) -> Set
|
||||
.of(JWSAlgorithm.RS256);
|
||||
|
||||
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = new DefaultJOSEObjectTypeVerifier<>(
|
||||
JOSEObjectType.JWT, null);
|
||||
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
|
||||
|
||||
private Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
|
||||
|
||||
@ -332,7 +331,8 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer)
|
||||
* .validateType(false)
|
||||
* .build();
|
||||
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
|
||||
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
|
||||
* new JwtIssuerValidator(issuer), JwtTypeValidator.jwt());
|
||||
* </code>
|
||||
*
|
||||
* <p>
|
||||
@ -550,8 +550,7 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
|
||||
private JWSAlgorithm jwsAlgorithm;
|
||||
|
||||
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = new DefaultJOSEObjectTypeVerifier<>(
|
||||
JOSEObjectType.JWT, null);
|
||||
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
|
||||
|
||||
private RSAPublicKey key;
|
||||
|
||||
@ -590,7 +589,8 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer)
|
||||
* .validateType(false)
|
||||
* .build();
|
||||
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
|
||||
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
|
||||
* new JwtIssuerValidator(issuer), JwtTypeValidator.jwt());
|
||||
* </code>
|
||||
*
|
||||
* <p>
|
||||
@ -686,8 +686,7 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
|
||||
private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;
|
||||
|
||||
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = new DefaultJOSEObjectTypeVerifier<>(
|
||||
JOSEObjectType.JWT, null);
|
||||
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
|
||||
|
||||
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
|
||||
|
||||
@ -723,7 +722,8 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer)
|
||||
* .validateType(false)
|
||||
* .build();
|
||||
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
|
||||
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
|
||||
* new JwtIssuerValidator(issuer), JwtTypeValidator.jwt());
|
||||
* </code>
|
||||
*
|
||||
* <p>
|
||||
|
@ -32,6 +32,7 @@ import javax.crypto.SecretKey;
|
||||
|
||||
import com.nimbusds.jose.Header;
|
||||
import com.nimbusds.jose.JOSEException;
|
||||
import com.nimbusds.jose.JOSEObjectType;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.JWSHeader;
|
||||
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.source.JWKSecurityContextJWKSet;
|
||||
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.JWSKeySelector;
|
||||
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
|
||||
@ -308,6 +311,12 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
*/
|
||||
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 Function<WebClient, Mono<String>> jwkSetUri;
|
||||
@ -315,6 +324,8 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
private Function<ReactiveRemoteJWKSource, Mono<Set<JWSAlgorithm>>> defaultAlgorithms = (source) -> Mono
|
||||
.just(Set.of(JWSAlgorithm.RS256));
|
||||
|
||||
private JOSEObjectTypeVerifier<JWKSecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
|
||||
|
||||
private Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
|
||||
|
||||
private WebClient webClient = WebClient.create();
|
||||
@ -349,6 +360,55 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
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
|
||||
* <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
|
||||
.flatMap((selector) -> {
|
||||
jwtProcessor.setJWSKeySelector(selector);
|
||||
jwtProcessor.setJWSTypeVerifier(this.typeVerifier);
|
||||
return this.jwtProcessorCustomizer.apply(source, jwtProcessor);
|
||||
})
|
||||
.map((processor) -> Tuples.of(processor, getExpectedJwsAlgorithms(processor.getJWSKeySelector())))
|
||||
.cache((processor) -> FOREVER, (ex) -> Duration.ZERO, () -> Duration.ZERO);
|
||||
return (jwt) -> {
|
||||
return jwtProcessorMono.flatMap((tuple) -> {
|
||||
JWTProcessor<JWKSecurityContext> processor = tuple.getT1();
|
||||
ConfigurableJWTProcessor<JWKSecurityContext> processor = tuple.getT1();
|
||||
Function<JWSAlgorithm, Boolean> expectedJwsAlgorithms = tuple.getT2();
|
||||
JWKSelector selector = createSelector(expectedJwsAlgorithms, jwt.getHeader());
|
||||
return source.get(selector)
|
||||
@ -476,10 +537,18 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
*/
|
||||
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 JWSAlgorithm jwsAlgorithm;
|
||||
|
||||
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
|
||||
|
||||
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
|
||||
|
||||
private PublicKeyReactiveJwtDecoderBuilder(RSAPublicKey key) {
|
||||
@ -505,6 +574,56 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
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
|
||||
* 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);
|
||||
DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
|
||||
jwtProcessor.setJWSKeySelector(jwsKeySelector);
|
||||
jwtProcessor.setJWSTypeVerifier(this.typeVerifier);
|
||||
// Spring Security validates the claim set independent from Nimbus
|
||||
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
|
||||
});
|
||||
@ -552,10 +672,18 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
*/
|
||||
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 JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;
|
||||
|
||||
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
|
||||
|
||||
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
|
||||
|
||||
private SecretKeyReactiveJwtDecoderBuilder(SecretKey secretKey) {
|
||||
@ -582,6 +710,55 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
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
|
||||
* ConfigurableJWTProcessor} before passing it to the build
|
||||
@ -610,6 +787,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
this.secretKey);
|
||||
DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
|
||||
jwtProcessor.setJWSKeySelector(jwsKeySelector);
|
||||
jwtProcessor.setJWSTypeVerifier(this.typeVerifier);
|
||||
// Spring Security validates the claim set independent from Nimbus
|
||||
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
|
||||
});
|
||||
@ -626,10 +804,18 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
*/
|
||||
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 JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256;
|
||||
|
||||
private JOSEObjectTypeVerifier<JWKSecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
|
||||
|
||||
private Consumer<ConfigurableJWTProcessor<JWKSecurityContext>> jwtProcessorCustomizer;
|
||||
|
||||
private JwkSourceReactiveJwtDecoderBuilder(Function<SignedJWT, Flux<JWK>> jwkSource) {
|
||||
@ -652,6 +838,55 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
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
|
||||
* ConfigurableJWTProcessor} before passing it to the build
|
||||
@ -681,6 +916,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
jwkSource);
|
||||
DefaultJWTProcessor<JWKSecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
|
||||
jwtProcessor.setJWSKeySelector(jwsKeySelector);
|
||||
jwtProcessor.setJWSTypeVerifier(this.typeVerifier);
|
||||
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
|
||||
});
|
||||
this.jwtProcessorCustomizer.accept(jwtProcessor);
|
||||
|
@ -18,12 +18,14 @@ package org.springframework.security.oauth2.jwt;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@ -68,6 +70,40 @@ public class JwtValidatorsTests {
|
||||
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")
|
||||
private boolean containsByType(OAuth2TokenValidator<Jwt> validator, Class<? extends OAuth2TokenValidator<?>> type) {
|
||||
DelegatingOAuth2TokenValidator<Jwt> delegatingOAuth2TokenValidator = (DelegatingOAuth2TokenValidator<Jwt>) validator;
|
||||
|
@ -849,7 +849,8 @@ public class NimbusJwtDecoderTests {
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(TestKeys.DEFAULT_SECRET_KEY)
|
||||
.validateType(false)
|
||||
.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());
|
||||
jwtDecoder.decode(jwt.serialize());
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ package org.springframework.security.oauth2.jwt;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.EncodedKeySpec;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
@ -38,6 +40,8 @@ import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.JWSHeader;
|
||||
import com.nimbusds.jose.JWSSigner;
|
||||
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.RSAKey;
|
||||
import com.nimbusds.jose.jwk.source.JWKSecurityContextJWKSet;
|
||||
@ -658,10 +662,60 @@ public class NimbusReactiveJwtDecoderTests {
|
||||
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)
|
||||
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);
|
||||
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);
|
||||
return signedJWT;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user