Skip to content

Commit 6d3b54d

Browse files
committed
Change Type Validation Default
NimbusJwtDecoder and NimbusReactiveJwtDecoder now use Spring Security's JwtTypeValidator by default instead of Nimbus's type validator. Closes gh-17181
1 parent 37a814b commit 6d3b54d

File tree

9 files changed

+111
-37
lines changed

9 files changed

+111
-37
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutAuthenticationProvider.java

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,6 @@
1818

1919
import java.util.function.Function;
2020

21-
import com.nimbusds.jose.JOSEObjectType;
22-
import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier;
23-
import com.nimbusds.jose.proc.JOSEObjectTypeVerifier;
24-
import com.nimbusds.jose.proc.SecurityContext;
25-
2621
import org.springframework.security.authentication.AuthenticationProvider;
2722
import org.springframework.security.authentication.AuthenticationServiceException;
2823
import org.springframework.security.core.Authentication;
@@ -38,6 +33,7 @@
3833
import org.springframework.security.oauth2.jwt.Jwt;
3934
import org.springframework.security.oauth2.jwt.JwtDecoder;
4035
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
36+
import org.springframework.security.oauth2.jwt.JwtTypeValidator;
4137
import org.springframework.security.oauth2.jwt.JwtValidators;
4238
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
4339
import org.springframework.util.Assert;
@@ -67,8 +63,10 @@ final class OidcBackChannelLogoutAuthenticationProvider implements Authenticatio
6763
* Construct an {@link OidcBackChannelLogoutAuthenticationProvider}
6864
*/
6965
OidcBackChannelLogoutAuthenticationProvider() {
66+
JwtTypeValidator type = new JwtTypeValidator("JWT", "logout+jwt");
67+
type.setAllowEmpty(true);
7068
Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidator = (clientRegistration) -> JwtValidators
71-
.createDefaultWithValidators(new OidcBackChannelLogoutTokenValidator(clientRegistration));
69+
.createDefaultWithValidators(type, new OidcBackChannelLogoutTokenValidator(clientRegistration));
7270
this.logoutTokenDecoderFactory = (clientRegistration) -> {
7371
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
7472
if (!StringUtils.hasText(jwkSetUri)) {
@@ -79,11 +77,7 @@ final class OidcBackChannelLogoutAuthenticationProvider implements Authenticatio
7977
null);
8078
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
8179
}
82-
JOSEObjectTypeVerifier<SecurityContext> typeVerifier = new DefaultJOSEObjectTypeVerifier<>(null,
83-
JOSEObjectType.JWT, new JOSEObjectType("logout+jwt"));
84-
NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
85-
.jwtProcessorCustomizer((processor) -> processor.setJWSTypeVerifier(typeVerifier))
86-
.build();
80+
NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
8781
decoder.setJwtValidator(jwtValidator.apply(clientRegistration));
8882
decoder.setClaimSetConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverter());
8983
return decoder;

config/src/main/java/org/springframework/security/config/web/server/OidcBackChannelLogoutReactiveAuthenticationManager.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@
1818

1919
import java.util.function.Function;
2020

21-
import com.nimbusds.jose.JOSEObjectType;
22-
import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier;
23-
import com.nimbusds.jose.proc.JOSEObjectTypeVerifier;
24-
import com.nimbusds.jose.proc.JWKSecurityContext;
2521
import reactor.core.publisher.Mono;
2622

2723
import org.springframework.security.authentication.AuthenticationProvider;
@@ -41,6 +37,7 @@
4137
import org.springframework.security.oauth2.jwt.Jwt;
4238
import org.springframework.security.oauth2.jwt.JwtDecoder;
4339
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
40+
import org.springframework.security.oauth2.jwt.JwtTypeValidator;
4441
import org.springframework.security.oauth2.jwt.JwtValidators;
4542
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
4643
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
@@ -72,8 +69,10 @@ final class OidcBackChannelLogoutReactiveAuthenticationManager implements Reacti
7269
* Construct an {@link OidcBackChannelLogoutReactiveAuthenticationManager}
7370
*/
7471
OidcBackChannelLogoutReactiveAuthenticationManager() {
72+
JwtTypeValidator type = new JwtTypeValidator("JWT", "logout+jwt");
73+
type.setAllowEmpty(true);
7574
Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidator = (clientRegistration) -> JwtValidators
76-
.createDefaultWithValidators(new OidcBackChannelLogoutTokenValidator(clientRegistration));
75+
.createDefaultWithValidators(type, new OidcBackChannelLogoutTokenValidator(clientRegistration));
7776
this.logoutTokenDecoderFactory = (clientRegistration) -> {
7877
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
7978
if (!StringUtils.hasText(jwkSetUri)) {
@@ -84,11 +83,7 @@ final class OidcBackChannelLogoutReactiveAuthenticationManager implements Reacti
8483
null);
8584
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
8685
}
87-
JOSEObjectTypeVerifier<JWKSecurityContext> typeVerifier = new DefaultJOSEObjectTypeVerifier<>(null,
88-
JOSEObjectType.JWT, new JOSEObjectType("logout+jwt"));
89-
NimbusReactiveJwtDecoder decoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri)
90-
.jwtProcessorCustomizer((processor) -> processor.setJWSTypeVerifier(typeVerifier))
91-
.build();
86+
NimbusReactiveJwtDecoder decoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build();
9287
decoder.setJwtValidator(jwtValidator.apply(clientRegistration));
9388
decoder.setClaimSetConverter(
9489
new ClaimTypeConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters()));
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,82 @@
11
= Reactive
22

33
If you have already performed the xref:migration/index.adoc[initial migration steps] for your Reactive application, you're now ready to perform steps specific to Reactive applications.
4+
5+
== Validate `typ` Header with `JwtTypeValidator`
6+
7+
If when following the 6.5 preparatory steps you set `validateTypes` to `false`, you can now remove it.
8+
You can also remove explicitly adding `JwtTypeValidator` to the list of defaults.
9+
10+
For example, change this:
11+
12+
[tabs]
13+
======
14+
Java::
15+
+
16+
[source,java,role="primary"]
17+
----
18+
@Bean
19+
JwtDecoder jwtDecoder() {
20+
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(location)
21+
.validateTypes(false) <1>
22+
// ... your remaining configuration
23+
.build();
24+
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
25+
new JwtIssuerValidator(location), JwtTypeValidator.jwt())); <2>
26+
return jwtDecoder;
27+
}
28+
----
29+
30+
Kotlin::
31+
+
32+
[source,kotlin,role="secondary"]
33+
----
34+
@Bean
35+
fun jwtDecoder(): JwtDecoder {
36+
val jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(location)
37+
.validateTypes(false) <1>
38+
// ... your remaining configuration
39+
.build()
40+
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
41+
JwtIssuerValidator(location), JwtTypeValidator.jwt())) <2>
42+
return jwtDecoder
43+
}
44+
----
45+
======
46+
<1> - Switch off Nimbus verifying the `typ`
47+
<2> - Add the default `typ` validator
48+
49+
to this:
50+
51+
[tabs]
52+
======
53+
Java::
54+
+
55+
[source,java,role="primary"]
56+
----
57+
@Bean
58+
NimbusReactiveJwtDecoder jwtDecoder() {
59+
NimbusJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(location)
60+
// ... your remaining configuration <1>
61+
.build();
62+
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(location)); <2>
63+
return jwtDecoder;
64+
}
65+
----
66+
67+
Kotlin::
68+
+
69+
[source,kotlin,role="secondary"]
70+
----
71+
@Bean
72+
fun jwtDecoder(): NimbusReactiveJwtDecoder {
73+
val jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(location)
74+
// ... your remaining configuration
75+
.build()
76+
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(location)) <2>
77+
return jwtDecoder
78+
}
79+
----
80+
======
81+
<1> - `validateTypes` now defaults to `false`
82+
<2> - `JwtTypeValidator#jwt` is added by all `createDefaultXXX` methods

oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtValidators.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ public static OAuth2TokenValidator<Jwt> createDefaultWithIssuer(String issuer) {
7575
* supplied
7676
*/
7777
public static OAuth2TokenValidator<Jwt> createDefault() {
78-
return new DelegatingOAuth2TokenValidator<>(
79-
Arrays.asList(new JwtTimestampValidator(), new X509CertificateThumbprintValidator(
78+
return new DelegatingOAuth2TokenValidator<>(Arrays.asList(JwtTypeValidator.jwt(), new JwtTimestampValidator(),
79+
new X509CertificateThumbprintValidator(
8080
X509CertificateThumbprintValidator.DEFAULT_X509_CERTIFICATE_SUPPLIER)));
8181
}
8282

@@ -104,6 +104,10 @@ public static OAuth2TokenValidator<Jwt> createDefaultWithValidators(List<OAuth2T
104104
if (jwtTimestampValidator == null) {
105105
tokenValidators.add(0, new JwtTimestampValidator());
106106
}
107+
JwtTypeValidator typeValidator = CollectionUtils.findValueOfType(tokenValidators, JwtTypeValidator.class);
108+
if (typeValidator == null) {
109+
tokenValidators.add(0, JwtTypeValidator.jwt());
110+
}
107111
return new DelegatingOAuth2TokenValidator<>(tokenValidators);
108112
}
109113

oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ public static final class JwkSetUriJwtDecoderBuilder {
279279
private Function<JWKSource<SecurityContext>, Set<JWSAlgorithm>> defaultAlgorithms = (source) -> Set
280280
.of(JWSAlgorithm.RS256);
281281

282-
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
282+
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = NO_TYPE_VERIFIER;
283283

284284
private final Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
285285

@@ -548,7 +548,7 @@ public static final class PublicKeyJwtDecoderBuilder {
548548

549549
private JWSAlgorithm jwsAlgorithm;
550550

551-
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
551+
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = NO_TYPE_VERIFIER;
552552

553553
private final RSAPublicKey key;
554554

@@ -680,7 +680,7 @@ public static final class SecretKeyJwtDecoderBuilder {
680680

681681
private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;
682682

683-
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
683+
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = NO_TYPE_VERIFIER;
684684

685685
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
686686

oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ public static final class JwkSetUriReactiveJwtDecoderBuilder {
324324
private Function<ReactiveRemoteJWKSource, Mono<Set<JWSAlgorithm>>> defaultAlgorithms = (source) -> Mono
325325
.just(Set.of(JWSAlgorithm.RS256));
326326

327-
private JOSEObjectTypeVerifier<JWKSecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
327+
private JOSEObjectTypeVerifier<JWKSecurityContext> typeVerifier = NO_TYPE_VERIFIER;
328328

329329
private Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
330330

@@ -547,7 +547,7 @@ public static final class PublicKeyReactiveJwtDecoderBuilder {
547547

548548
private JWSAlgorithm jwsAlgorithm;
549549

550-
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
550+
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = NO_TYPE_VERIFIER;
551551

552552
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
553553

@@ -682,7 +682,7 @@ public static final class SecretKeyReactiveJwtDecoderBuilder {
682682

683683
private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;
684684

685-
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
685+
private JOSEObjectTypeVerifier<SecurityContext> typeVerifier = NO_TYPE_VERIFIER;
686686

687687
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
688688

@@ -814,7 +814,7 @@ public static final class JwkSourceReactiveJwtDecoderBuilder {
814814

815815
private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256;
816816

817-
private JOSEObjectTypeVerifier<JWKSecurityContext> typeVerifier = JWT_TYPE_VERIFIER;
817+
private JOSEObjectTypeVerifier<JWKSecurityContext> typeVerifier = NO_TYPE_VERIFIER;
818818

819819
private Consumer<ConfigurableJWTProcessor<JWKSecurityContext>> jwtProcessorCustomizer;
820820

oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtValidatorsTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ public void createWhenJwtTimestampValidatorIsPresentThenCreateDefaultValidatorWi
6262

6363
assertThat(containsByType(validator, JwtTimestampValidator.class)).isTrue();
6464
assertThat(containsByType(validator, X509CertificateThumbprintValidator.class)).isTrue();
65-
assertThat(Objects.requireNonNull(tokenValidators).size()).isEqualTo(2);
65+
assertThat(containsByType(validator, JwtTypeValidator.class)).isTrue();
66+
assertThat(Objects.requireNonNull(tokenValidators).size()).isEqualTo(3);
6667
}
6768

6869
@Test

oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -458,10 +458,8 @@ public void withPublicKeyWhenUsingCustomTypeHeaderThenSuccessfullyDecodes() thro
458458
// @formatter:off
459459
NimbusJwtDecoder decoder = NimbusJwtDecoder.withPublicKey(publicKey)
460460
.signatureAlgorithm(SignatureAlgorithm.RS256)
461-
.jwtProcessorCustomizer((p) -> p
462-
.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("JWS")))
463-
)
464461
.build();
462+
decoder.setJwtValidator(JwtValidators.createDefaultWithValidators(new JwtTypeValidator("JWS")));
465463
// @formatter:on
466464
assertThat(decoder.decode(signedJwt.serialize()).hasClaim(JwtClaimNames.EXP)).isNotNull();
467465
}
@@ -575,10 +573,8 @@ public void withSecretKeyWhenUsingCustomTypeHeaderThenSuccessfullyDecodes() thro
575573
// @formatter:off
576574
NimbusJwtDecoder decoder = NimbusJwtDecoder.withSecretKey(secretKey)
577575
.macAlgorithm(MacAlgorithm.HS256)
578-
.jwtProcessorCustomizer((p) -> p
579-
.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("JWS")))
580-
)
581576
.build();
577+
decoder.setJwtValidator(JwtValidators.createDefaultWithValidators(new JwtTypeValidator("JWS")));
582578
// @formatter:on
583579
assertThat(decoder.decode(signedJwt.serialize()).hasClaim(JwtClaimNames.EXP)).isNotNull();
584580
}
@@ -837,6 +833,7 @@ public void decodeWhenPublicKeyValidateTypeFalseThenSkipsNimbusTypeValidation()
837833
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY)
838834
.validateType(false)
839835
.build();
836+
jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success());
840837
RSAPrivateKey privateKey = TestKeys.DEFAULT_PRIVATE_KEY;
841838
SignedJWT jwt = signedJwt(privateKey,
842839
new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JOSE).build(),
@@ -849,6 +846,7 @@ public void decodeWhenSecretKeyValidateTypeFalseThenSkipsNimbusTypeValidation()
849846
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(TestKeys.DEFAULT_SECRET_KEY)
850847
.validateType(false)
851848
.build();
849+
jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success());
852850
SignedJWT jwt = signedJwt(TestKeys.DEFAULT_SECRET_KEY,
853851
new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JOSE).build(),
854852
new JWTClaimsSet.Builder().subject("subject").build());

oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoderTests.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,7 @@ public void decodeWhenPublicKeyValidateTypeFalseThenSkipsNimbusTypeValidation()
667667
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY)
668668
.validateType(false)
669669
.build();
670+
jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success());
670671
RSAPrivateKey privateKey = TestKeys.DEFAULT_PRIVATE_KEY;
671672
SignedJWT jwt = signedJwt(privateKey,
672673
new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JOSE).build(),
@@ -679,6 +680,7 @@ public void decodeWhenSecretKeyValidateTypeFalseThenSkipsNimbusTypeValidation()
679680
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withSecretKey(TestKeys.DEFAULT_SECRET_KEY)
680681
.validateType(false)
681682
.build();
683+
jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success());
682684
SignedJWT jwt = signedJwt(TestKeys.DEFAULT_SECRET_KEY,
683685
new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JOSE).build(),
684686
new JWTClaimsSet.Builder().subject("subject").build());
@@ -693,6 +695,7 @@ public void decodeWhenJwkSourceValidateTypeFalseThenSkipsNimbusTypeValidation()
693695
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withJwkSource((jwt) -> Flux.just(jwk))
694696
.validateType(false)
695697
.build();
698+
jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success());
696699
SignedJWT jwt = signedJwt(TestKeys.DEFAULT_PRIVATE_KEY,
697700
new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JOSE).build(),
698701
new JWTClaimsSet.Builder().subject("subject").build());

0 commit comments

Comments
 (0)