diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutAuthenticationProvider.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutAuthenticationProvider.java index 07debd7420..3209736f51 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutAuthenticationProvider.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutAuthenticationProvider.java @@ -18,11 +18,6 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.cl import java.util.function.Function; -import com.nimbusds.jose.JOSEObjectType; -import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier; -import com.nimbusds.jose.proc.JOSEObjectTypeVerifier; -import com.nimbusds.jose.proc.SecurityContext; - import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; @@ -38,6 +33,7 @@ import org.springframework.security.oauth2.jwt.BadJwtException; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoderFactory; +import org.springframework.security.oauth2.jwt.JwtTypeValidator; import org.springframework.security.oauth2.jwt.JwtValidators; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.util.Assert; @@ -67,8 +63,10 @@ final class OidcBackChannelLogoutAuthenticationProvider implements Authenticatio * Construct an {@link OidcBackChannelLogoutAuthenticationProvider} */ OidcBackChannelLogoutAuthenticationProvider() { + JwtTypeValidator type = new JwtTypeValidator("JWT", "logout+jwt"); + type.setAllowEmpty(true); Function> jwtValidator = (clientRegistration) -> JwtValidators - .createDefaultWithValidators(new OidcBackChannelLogoutTokenValidator(clientRegistration)); + .createDefaultWithValidators(type, new OidcBackChannelLogoutTokenValidator(clientRegistration)); this.logoutTokenDecoderFactory = (clientRegistration) -> { String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri(); if (!StringUtils.hasText(jwkSetUri)) { @@ -79,11 +77,7 @@ final class OidcBackChannelLogoutAuthenticationProvider implements Authenticatio null); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } - JOSEObjectTypeVerifier typeVerifier = new DefaultJOSEObjectTypeVerifier<>(null, - JOSEObjectType.JWT, new JOSEObjectType("logout+jwt")); - NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri) - .jwtProcessorCustomizer((processor) -> processor.setJWSTypeVerifier(typeVerifier)) - .build(); + NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build(); decoder.setJwtValidator(jwtValidator.apply(clientRegistration)); decoder.setClaimSetConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverter()); return decoder; diff --git a/config/src/main/java/org/springframework/security/config/web/server/OidcBackChannelLogoutReactiveAuthenticationManager.java b/config/src/main/java/org/springframework/security/config/web/server/OidcBackChannelLogoutReactiveAuthenticationManager.java index 04334be1bf..155d80f01a 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/OidcBackChannelLogoutReactiveAuthenticationManager.java +++ b/config/src/main/java/org/springframework/security/config/web/server/OidcBackChannelLogoutReactiveAuthenticationManager.java @@ -18,10 +18,6 @@ package org.springframework.security.config.web.server; import java.util.function.Function; -import com.nimbusds.jose.JOSEObjectType; -import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier; -import com.nimbusds.jose.proc.JOSEObjectTypeVerifier; -import com.nimbusds.jose.proc.JWKSecurityContext; import reactor.core.publisher.Mono; import org.springframework.security.authentication.AuthenticationProvider; @@ -41,6 +37,7 @@ import org.springframework.security.oauth2.jwt.BadJwtException; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoderFactory; +import org.springframework.security.oauth2.jwt.JwtTypeValidator; import org.springframework.security.oauth2.jwt.JwtValidators; import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; @@ -72,8 +69,10 @@ final class OidcBackChannelLogoutReactiveAuthenticationManager implements Reacti * Construct an {@link OidcBackChannelLogoutReactiveAuthenticationManager} */ OidcBackChannelLogoutReactiveAuthenticationManager() { + JwtTypeValidator type = new JwtTypeValidator("JWT", "logout+jwt"); + type.setAllowEmpty(true); Function> jwtValidator = (clientRegistration) -> JwtValidators - .createDefaultWithValidators(new OidcBackChannelLogoutTokenValidator(clientRegistration)); + .createDefaultWithValidators(type, new OidcBackChannelLogoutTokenValidator(clientRegistration)); this.logoutTokenDecoderFactory = (clientRegistration) -> { String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri(); if (!StringUtils.hasText(jwkSetUri)) { @@ -84,11 +83,7 @@ final class OidcBackChannelLogoutReactiveAuthenticationManager implements Reacti null); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } - JOSEObjectTypeVerifier typeVerifier = new DefaultJOSEObjectTypeVerifier<>(null, - JOSEObjectType.JWT, new JOSEObjectType("logout+jwt")); - NimbusReactiveJwtDecoder decoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri) - .jwtProcessorCustomizer((processor) -> processor.setJWSTypeVerifier(typeVerifier)) - .build(); + NimbusReactiveJwtDecoder decoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build(); decoder.setJwtValidator(jwtValidator.apply(clientRegistration)); decoder.setClaimSetConverter( new ClaimTypeConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters())); diff --git a/docs/modules/ROOT/pages/migration/reactive.adoc b/docs/modules/ROOT/pages/migration/reactive.adoc index 17d33ff586..44caa91592 100644 --- a/docs/modules/ROOT/pages/migration/reactive.adoc +++ b/docs/modules/ROOT/pages/migration/reactive.adoc @@ -1,3 +1,82 @@ = Reactive 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. + +== Validate `typ` Header with `JwtTypeValidator` + +If when following the 6.5 preparatory steps you set `validateTypes` to `false`, you can now remove it. +You can also remove explicitly adding `JwtTypeValidator` to the list of defaults. + +For example, change this: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +JwtDecoder jwtDecoder() { + NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.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 = NimbusReactiveJwtDecoder.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` +<2> - Add the default `typ` validator + +to this: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +NimbusReactiveJwtDecoder jwtDecoder() { + NimbusJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(location) + // ... your remaining configuration <1> + .build(); + jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(location)); <2> + return jwtDecoder; +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun jwtDecoder(): NimbusReactiveJwtDecoder { + val jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(location) + // ... your remaining configuration + .build() + jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(location)) <2> + return jwtDecoder +} +---- +====== +<1> - `validateTypes` now defaults to `false` +<2> - `JwtTypeValidator#jwt` is added by all `createDefaultXXX` methods diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtValidators.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtValidators.java index b958328c8d..a904684398 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtValidators.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtValidators.java @@ -75,8 +75,8 @@ public final class JwtValidators { * supplied */ public static OAuth2TokenValidator createDefault() { - return new DelegatingOAuth2TokenValidator<>( - Arrays.asList(new JwtTimestampValidator(), new X509CertificateThumbprintValidator( + return new DelegatingOAuth2TokenValidator<>(Arrays.asList(JwtTypeValidator.jwt(), new JwtTimestampValidator(), + new X509CertificateThumbprintValidator( X509CertificateThumbprintValidator.DEFAULT_X509_CERTIFICATE_SUPPLIER))); } @@ -104,6 +104,10 @@ public final class JwtValidators { if (jwtTimestampValidator == null) { tokenValidators.add(0, new JwtTimestampValidator()); } + JwtTypeValidator typeValidator = CollectionUtils.findValueOfType(tokenValidators, JwtTypeValidator.class); + if (typeValidator == null) { + tokenValidators.add(0, JwtTypeValidator.jwt()); + } return new DelegatingOAuth2TokenValidator<>(tokenValidators); } diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java index eb5efbabec..66f0b70e45 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java @@ -279,7 +279,7 @@ public final class NimbusJwtDecoder implements JwtDecoder { private Function, Set> defaultAlgorithms = (source) -> Set .of(JWSAlgorithm.RS256); - private JOSEObjectTypeVerifier typeVerifier = JWT_TYPE_VERIFIER; + private JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; private final Set signatureAlgorithms = new HashSet<>(); @@ -548,7 +548,7 @@ public final class NimbusJwtDecoder implements JwtDecoder { private JWSAlgorithm jwsAlgorithm; - private JOSEObjectTypeVerifier typeVerifier = JWT_TYPE_VERIFIER; + private JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; private final RSAPublicKey key; @@ -680,7 +680,7 @@ public final class NimbusJwtDecoder implements JwtDecoder { private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256; - private JOSEObjectTypeVerifier typeVerifier = JWT_TYPE_VERIFIER; + private JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; private Consumer> jwtProcessorCustomizer; diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java index 32f97c1355..c76532689d 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java @@ -324,7 +324,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { private Function>> defaultAlgorithms = (source) -> Mono .just(Set.of(JWSAlgorithm.RS256)); - private JOSEObjectTypeVerifier typeVerifier = JWT_TYPE_VERIFIER; + private JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; private Set signatureAlgorithms = new HashSet<>(); @@ -547,7 +547,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { private JWSAlgorithm jwsAlgorithm; - private JOSEObjectTypeVerifier typeVerifier = JWT_TYPE_VERIFIER; + private JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; private Consumer> jwtProcessorCustomizer; @@ -682,7 +682,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256; - private JOSEObjectTypeVerifier typeVerifier = JWT_TYPE_VERIFIER; + private JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; private Consumer> jwtProcessorCustomizer; @@ -814,7 +814,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256; - private JOSEObjectTypeVerifier typeVerifier = JWT_TYPE_VERIFIER; + private JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; private Consumer> jwtProcessorCustomizer; diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtValidatorsTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtValidatorsTests.java index 33174e9181..dad08edb69 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtValidatorsTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtValidatorsTests.java @@ -62,7 +62,8 @@ public class JwtValidatorsTests { assertThat(containsByType(validator, JwtTimestampValidator.class)).isTrue(); assertThat(containsByType(validator, X509CertificateThumbprintValidator.class)).isTrue(); - assertThat(Objects.requireNonNull(tokenValidators).size()).isEqualTo(2); + assertThat(containsByType(validator, JwtTypeValidator.class)).isTrue(); + assertThat(Objects.requireNonNull(tokenValidators).size()).isEqualTo(3); } @Test diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java index 9b7805a0d8..91db639c45 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java @@ -458,10 +458,8 @@ public class NimbusJwtDecoderTests { // @formatter:off NimbusJwtDecoder decoder = NimbusJwtDecoder.withPublicKey(publicKey) .signatureAlgorithm(SignatureAlgorithm.RS256) - .jwtProcessorCustomizer((p) -> p - .setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("JWS"))) - ) .build(); + decoder.setJwtValidator(JwtValidators.createDefaultWithValidators(new JwtTypeValidator("JWS"))); // @formatter:on assertThat(decoder.decode(signedJwt.serialize()).hasClaim(JwtClaimNames.EXP)).isNotNull(); } @@ -575,10 +573,8 @@ public class NimbusJwtDecoderTests { // @formatter:off NimbusJwtDecoder decoder = NimbusJwtDecoder.withSecretKey(secretKey) .macAlgorithm(MacAlgorithm.HS256) - .jwtProcessorCustomizer((p) -> p - .setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("JWS"))) - ) .build(); + decoder.setJwtValidator(JwtValidators.createDefaultWithValidators(new JwtTypeValidator("JWS"))); // @formatter:on assertThat(decoder.decode(signedJwt.serialize()).hasClaim(JwtClaimNames.EXP)).isNotNull(); } @@ -837,6 +833,7 @@ public class NimbusJwtDecoderTests { NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY) .validateType(false) .build(); + jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success()); RSAPrivateKey privateKey = TestKeys.DEFAULT_PRIVATE_KEY; SignedJWT jwt = signedJwt(privateKey, new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JOSE).build(), @@ -849,6 +846,7 @@ public class NimbusJwtDecoderTests { NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(TestKeys.DEFAULT_SECRET_KEY) .validateType(false) .build(); + jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success()); SignedJWT jwt = signedJwt(TestKeys.DEFAULT_SECRET_KEY, new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JOSE).build(), new JWTClaimsSet.Builder().subject("subject").build()); diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoderTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoderTests.java index 6ec57ab2cc..bee51921a6 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoderTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoderTests.java @@ -667,6 +667,7 @@ public class NimbusReactiveJwtDecoderTests { NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY) .validateType(false) .build(); + jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success()); RSAPrivateKey privateKey = TestKeys.DEFAULT_PRIVATE_KEY; SignedJWT jwt = signedJwt(privateKey, new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JOSE).build(), @@ -679,6 +680,7 @@ public class NimbusReactiveJwtDecoderTests { NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withSecretKey(TestKeys.DEFAULT_SECRET_KEY) .validateType(false) .build(); + jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success()); SignedJWT jwt = signedJwt(TestKeys.DEFAULT_SECRET_KEY, new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JOSE).build(), new JWTClaimsSet.Builder().subject("subject").build()); @@ -693,6 +695,7 @@ public class NimbusReactiveJwtDecoderTests { NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withJwkSource((jwt) -> Flux.just(jwk)) .validateType(false) .build(); + jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success()); SignedJWT jwt = signedJwt(TestKeys.DEFAULT_PRIVATE_KEY, new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JOSE).build(), new JWTClaimsSet.Builder().subject("subject").build());