From d31fff11b3bb91e484228f29af469a9a0661dd32 Mon Sep 17 00:00:00 2001 From: Jan Oopkaup Date: Mon, 22 Jun 2020 13:58:56 +0200 Subject: [PATCH] Add Post-Processor for JWTProcessor Configuration Extends all existing builders in NimbusJwtDecoder and NimbusReactiveJwtDecoder with a post-processor hook to apply changes on the JWTProcessor used for token verification. Test cases added show how this is used to configure the JWTProcessor to allow additional JWT typ headers. Closes gh-8730 --- .../security/oauth2/jwt/NimbusJwtDecoder.java | 54 +++++++++++++ .../oauth2/jwt/NimbusReactiveJwtDecoder.java | 73 +++++++++++++++++ .../oauth2/jwt/NimbusJwtDecoderTests.java | 72 +++++++++++++++++ .../jwt/NimbusReactiveJwtDecoderTests.java | 81 +++++++++++++++++++ 4 files changed, 280 insertions(+) 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 44daabd13c..03993b32e6 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 @@ -219,10 +219,12 @@ public final class NimbusJwtDecoder implements JwtDecoder { private Set signatureAlgorithms = new HashSet<>(); private RestOperations restOperations = new RestTemplate(); private Cache cache; + private Consumer> jwtProcessorCustomizer; private JwkSetUriJwtDecoderBuilder(String jwkSetUri) { Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty"); this.jwkSetUri = jwkSetUri; + this.jwtProcessorCustomizer = (processor) -> {}; } /** @@ -282,6 +284,20 @@ public final class NimbusJwtDecoder implements JwtDecoder { return this; } + /** + * Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before + * passing it to the build {@link NimbusJwtDecoder}. + * + * @param jwtProcessorCustomizer the callback used to alter the processor + * @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations + * @since 5.4 + */ + public JwkSetUriJwtDecoderBuilder jwtProcessorCustomizer(Consumer> jwtProcessorCustomizer) { + Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null"); + this.jwtProcessorCustomizer = jwtProcessorCustomizer; + return this; + } + JWSKeySelector jwsKeySelector(JWKSource jwkSource) { if (this.signatureAlgorithms.isEmpty()) { return new JWSVerificationKeySelector<>(JWSAlgorithm.RS256, jwkSource); @@ -312,6 +328,8 @@ public final class NimbusJwtDecoder implements JwtDecoder { // Spring Security validates the claim set independent from Nimbus jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { }); + this.jwtProcessorCustomizer.accept(jwtProcessor); + return jwtProcessor; } @@ -414,11 +432,13 @@ public final class NimbusJwtDecoder implements JwtDecoder { public static final class PublicKeyJwtDecoderBuilder { private JWSAlgorithm jwsAlgorithm; private RSAPublicKey key; + private Consumer> jwtProcessorCustomizer; private PublicKeyJwtDecoderBuilder(RSAPublicKey key) { Assert.notNull(key, "key cannot be null"); this.jwsAlgorithm = JWSAlgorithm.RS256; this.key = key; + this.jwtProcessorCustomizer = (processor) -> {}; } /** @@ -437,6 +457,20 @@ public final class NimbusJwtDecoder implements JwtDecoder { return this; } + /** + * Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before + * passing it to the build {@link NimbusJwtDecoder}. + * + * @param jwtProcessorCustomizer the callback used to alter the processor + * @return a {@link PublicKeyJwtDecoderBuilder} for further configurations + * @since 5.4 + */ + public PublicKeyJwtDecoderBuilder jwtProcessorCustomizer(Consumer> jwtProcessorCustomizer) { + Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null"); + this.jwtProcessorCustomizer = jwtProcessorCustomizer; + return this; + } + JWTProcessor processor() { if (!JWSAlgorithm.Family.RSA.contains(this.jwsAlgorithm)) { throw new IllegalStateException("The provided key is of type RSA; " + @@ -452,6 +486,8 @@ public final class NimbusJwtDecoder implements JwtDecoder { // Spring Security validates the claim set independent from Nimbus jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { }); + this.jwtProcessorCustomizer.accept(jwtProcessor); + return jwtProcessor; } @@ -471,10 +507,12 @@ public final class NimbusJwtDecoder implements JwtDecoder { public static final class SecretKeyJwtDecoderBuilder { private final SecretKey secretKey; private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256; + private Consumer> jwtProcessorCustomizer; private SecretKeyJwtDecoderBuilder(SecretKey secretKey) { Assert.notNull(secretKey, "secretKey cannot be null"); this.secretKey = secretKey; + this.jwtProcessorCustomizer = (processor) -> {}; } /** @@ -494,6 +532,20 @@ public final class NimbusJwtDecoder implements JwtDecoder { return this; } + /** + * Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before + * passing it to the build {@link NimbusJwtDecoder}. + * + * @param jwtProcessorCustomizer the callback used to alter the processor + * @return a {@link SecretKeyJwtDecoderBuilder} for further configurations + * @since 5.4 + */ + public SecretKeyJwtDecoderBuilder jwtProcessorCustomizer(Consumer> jwtProcessorCustomizer) { + Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null"); + this.jwtProcessorCustomizer = jwtProcessorCustomizer; + return this; + } + /** * Build the configured {@link NimbusJwtDecoder}. * @@ -512,6 +564,8 @@ public final class NimbusJwtDecoder implements JwtDecoder { // Spring Security validates the claim set independent from Nimbus jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { }); + this.jwtProcessorCustomizer.accept(jwtProcessor); + return jwtProcessor; } } 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 fa82a3899c..934a8297e9 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 @@ -44,6 +44,7 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.JWTParser; import com.nimbusds.jwt.PlainJWT; import com.nimbusds.jwt.SignedJWT; +import com.nimbusds.jwt.proc.ConfigurableJWTProcessor; import com.nimbusds.jwt.proc.DefaultJWTProcessor; import com.nimbusds.jwt.proc.JWTProcessor; import reactor.core.publisher.Flux; @@ -245,10 +246,12 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { private final String jwkSetUri; private Set signatureAlgorithms = new HashSet<>(); private WebClient webClient = WebClient.create(); + private Consumer> jwtProcessorCustomizer; private JwkSetUriReactiveJwtDecoderBuilder(String jwkSetUri) { Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty"); this.jwkSetUri = jwkSetUri; + this.jwtProcessorCustomizer = (processor) -> {}; } /** @@ -294,6 +297,20 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { return this; } + /** + * Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before + * passing it to the build {@link NimbusReactiveJwtDecoder}. + * + * @param jwtProcessorCustomizer the callback used to alter the processor + * @return a {@link JwkSetUriReactiveJwtDecoderBuilder} for further configurations + * @since 5.4 + */ + public JwkSetUriReactiveJwtDecoderBuilder jwtProcessorCustomizer(Consumer> jwtProcessorCustomizer) { + Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null"); + this.jwtProcessorCustomizer = jwtProcessorCustomizer; + return this; + } + /** * Build the configured {@link NimbusReactiveJwtDecoder}. * @@ -323,6 +340,8 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { jwtProcessor.setJWSKeySelector(jwsKeySelector); jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {}); + this.jwtProcessorCustomizer.accept(jwtProcessor); + ReactiveRemoteJWKSource source = new ReactiveRemoteJWKSource(this.jwkSetUri); source.setWebClient(this.webClient); @@ -360,11 +379,13 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { public static final class PublicKeyReactiveJwtDecoderBuilder { private final RSAPublicKey key; private JWSAlgorithm jwsAlgorithm; + private Consumer> jwtProcessorCustomizer; private PublicKeyReactiveJwtDecoderBuilder(RSAPublicKey key) { Assert.notNull(key, "key cannot be null"); this.key = key; this.jwsAlgorithm = JWSAlgorithm.RS256; + this.jwtProcessorCustomizer = (processor) -> {}; } /** @@ -382,6 +403,20 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { return this; } + /** + * Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before + * passing it to the build {@link NimbusReactiveJwtDecoder}. + * + * @param jwtProcessorCustomizer the callback used to alter the processor + * @return a {@link PublicKeyReactiveJwtDecoderBuilder} for further configurations + * @since 5.4 + */ + public PublicKeyReactiveJwtDecoderBuilder jwtProcessorCustomizer(Consumer> jwtProcessorCustomizer) { + Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null"); + this.jwtProcessorCustomizer = jwtProcessorCustomizer; + return this; + } + /** * Build the configured {@link NimbusReactiveJwtDecoder}. * @@ -406,6 +441,8 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { // Spring Security validates the claim set independent from Nimbus jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { }); + this.jwtProcessorCustomizer.accept(jwtProcessor); + return jwt -> Mono.just(createClaimsSet(jwtProcessor, jwt, null)); } } @@ -418,10 +455,12 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { public static final class SecretKeyReactiveJwtDecoderBuilder { private final SecretKey secretKey; private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256; + private Consumer> jwtProcessorCustomizer; private SecretKeyReactiveJwtDecoderBuilder(SecretKey secretKey) { Assert.notNull(secretKey, "secretKey cannot be null"); this.secretKey = secretKey; + this.jwtProcessorCustomizer = (processor) -> {}; } /** @@ -441,6 +480,20 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { return this; } + /** + * Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before + * passing it to the build {@link NimbusReactiveJwtDecoder}. + * + * @param jwtProcessorCustomizer the callback used to alter the processor + * @return a {@link SecretKeyReactiveJwtDecoderBuilder} for further configurations + * @since 5.4 + */ + public SecretKeyReactiveJwtDecoderBuilder jwtProcessorCustomizer(Consumer> jwtProcessorCustomizer) { + Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null"); + this.jwtProcessorCustomizer = jwtProcessorCustomizer; + return this; + } + /** * Build the configured {@link NimbusReactiveJwtDecoder}. * @@ -459,6 +512,8 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { // Spring Security validates the claim set independent from Nimbus jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { }); + this.jwtProcessorCustomizer.accept(jwtProcessor); + return jwt -> Mono.just(createClaimsSet(jwtProcessor, jwt, null)); } } @@ -471,10 +526,12 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { public static final class JwkSourceReactiveJwtDecoderBuilder { private final Function> jwkSource; private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256; + private Consumer> jwtProcessorCustomizer; private JwkSourceReactiveJwtDecoderBuilder(Function> jwkSource) { Assert.notNull(jwkSource, "jwkSource cannot be null"); this.jwkSource = jwkSource; + this.jwtProcessorCustomizer = (processor) -> {}; } /** @@ -490,6 +547,20 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { return this; } + /** + * Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before + * passing it to the build {@link NimbusReactiveJwtDecoder}. + * + * @param jwtProcessorCustomizer the callback used to alter the processor + * @return a {@link JwkSourceReactiveJwtDecoderBuilder} for further configurations + * @since 5.4 + */ + public JwkSourceReactiveJwtDecoderBuilder jwtProcessorCustomizer(Consumer> jwtProcessorCustomizer) { + Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null"); + this.jwtProcessorCustomizer = jwtProcessorCustomizer; + return this; + } + /** * Build the configured {@link NimbusReactiveJwtDecoder}. * @@ -507,6 +578,8 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { jwtProcessor.setJWSKeySelector(jwsKeySelector); jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {}); + this.jwtProcessorCustomizer.accept(jwtProcessor); + return jwt -> { if (jwt instanceof SignedJWT) { return this.jwkSource.apply((SignedJWT) jwt) 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 a6ff351287..a6f5b5e136 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 @@ -35,6 +35,7 @@ import java.util.Map; import java.util.concurrent.Callable; import javax.crypto.SecretKey; +import com.nimbusds.jose.JOSEObjectType; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSSigner; @@ -42,6 +43,7 @@ import com.nimbusds.jose.crypto.MACSigner; import com.nimbusds.jose.crypto.RSASSASigner; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.BadJOSEException; +import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier; import com.nimbusds.jose.proc.JWSKeySelector; import com.nimbusds.jose.proc.JWSVerificationKeySelector; import com.nimbusds.jose.proc.SecurityContext; @@ -346,6 +348,30 @@ public class NimbusJwtDecoderTests { .isInstanceOf(BadJwtException.class); } + // gh-8730 + @Test + public void withPublicKeyWhenUsingCustomTypeHeaderThenSuccessfullyDecodes() throws Exception { + RSAPublicKey publicKey = TestKeys.DEFAULT_PUBLIC_KEY; + RSAPrivateKey privateKey = TestKeys.DEFAULT_PRIVATE_KEY; + JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).type(new JOSEObjectType("JWS")).build(); + JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() + .expirationTime(Date.from(Instant.now().plusSeconds(60))) + .build(); + SignedJWT signedJwt = signedJwt(privateKey, header, claimsSet); + NimbusJwtDecoder decoder = withPublicKey(publicKey) + .signatureAlgorithm(SignatureAlgorithm.RS256) + .jwtProcessorCustomizer(p -> p.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("JWS")))) + .build(); + assertThat(decoder.decode(signedJwt.serialize()).containsClaim(JwtClaimNames.EXP)).isNotNull(); + } + + @Test + public void withPublicKeyWhenJwtProcessorCustomizerNullThenThrowsIllegalArgumentException() { + assertThatThrownBy(() -> withPublicKey(key()).jwtProcessorCustomizer(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("jwtProcessorCustomizer cannot be null"); + } + @Test public void withSecretKeyWhenNullThenThrowsIllegalArgumentException() { assertThatThrownBy(() -> withSecretKey(null)) @@ -407,6 +433,30 @@ public class NimbusJwtDecoderTests { .isEqualTo("test-subject"); } + // gh-8730 + @Test + public void withSecretKeyWhenUsingCustomTypeHeaderThenSuccessfullyDecodes() throws Exception { + SecretKey secretKey = TestKeys.DEFAULT_SECRET_KEY; + JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.HS256).type(new JOSEObjectType("JWS")).build(); + JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() + .expirationTime(Date.from(Instant.now().plusSeconds(60))) + .build(); + SignedJWT signedJwt = signedJwt(secretKey, header, claimsSet); + NimbusJwtDecoder decoder = withSecretKey(secretKey) + .macAlgorithm(MacAlgorithm.HS256) + .jwtProcessorCustomizer(p -> p.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("JWS")))) + .build(); + assertThat(decoder.decode(signedJwt.serialize()).containsClaim(JwtClaimNames.EXP)).isNotNull(); + } + + @Test + public void withSecretKeyWhenJwtProcessorCustomizerNullThenThrowsIllegalArgumentException() { + SecretKey secretKey = TestKeys.DEFAULT_SECRET_KEY; + assertThatThrownBy(() -> withSecretKey(secretKey).jwtProcessorCustomizer(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("jwtProcessorCustomizer cannot be null"); + } + @Test public void jwsKeySelectorWhenNoAlgorithmThenReturnsRS256Selector() { JWKSource jwkSource = mock(JWKSource.class); @@ -524,6 +574,28 @@ public class NimbusJwtDecoderTests { .hasMessageContaining("An error occurred while attempting to decode the Jwt"); } + // gh-8730 + @Test + public void withJwkSetUriWhenUsingCustomTypeHeaderThenRefuseOmittedType() throws Exception { + RestOperations restOperations = mock(RestOperations.class); + when(restOperations.exchange(any(RequestEntity.class), eq(String.class))) + .thenReturn(new ResponseEntity<>(JWK_SET, HttpStatus.OK)); + NimbusJwtDecoder jwtDecoder = withJwkSetUri(JWK_SET_URI) + .restOperations(restOperations) + .jwtProcessorCustomizer(p -> p.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("JWS")))) + .build(); + assertThatCode(() -> jwtDecoder.decode(SIGNED_JWT)) + .isInstanceOf(BadJwtException.class) + .hasMessageContaining("An error occurred while attempting to decode the Jwt: Required JOSE header \"typ\" (type) parameter is missing"); + } + + @Test + public void withJwkSetUriWhenJwtProcessorCustomizerNullThenThrowsIllegalArgumentException() { + assertThatThrownBy(() -> withJwkSetUri(JWK_SET_URI).jwtProcessorCustomizer(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("jwtProcessorCustomizer cannot be null"); + } + private RSAPublicKey key() throws InvalidKeySpecException { byte[] decoded = Base64.getDecoder().decode(VERIFY_KEY.getBytes()); EncodedKeySpec spec = new X509EncodedKeySpec(decoded); 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 3109031fdb..d701b7b6f3 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 @@ -31,12 +31,14 @@ import java.util.Date; import java.util.Map; import javax.crypto.SecretKey; +import com.nimbusds.jose.JOSEObjectType; 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.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; +import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier; import com.nimbusds.jose.proc.JWKSecurityContext; import com.nimbusds.jose.proc.JWSKeySelector; import com.nimbusds.jose.proc.JWSVerificationKeySelector; @@ -44,6 +46,7 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; +import org.assertj.core.api.AssertionsForClassTypes; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; @@ -269,6 +272,13 @@ public class NimbusReactiveJwtDecoderTests { assertThatCode(() -> builder.jwsAlgorithm(null)).isInstanceOf(IllegalArgumentException.class); } + @Test + public void withJwkSetUriWhenJwtProcessorCustomizerNullThenThrowsIllegalArgumentException() { + assertThatCode(() -> withJwkSetUri(jwkSetUri).jwtProcessorCustomizer(null).build()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("jwtProcessorCustomizer cannot be null"); + } + @Test public void restOperationsWhenNullThenThrowsException() { NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder builder = withJwkSetUri(this.jwkSetUri); @@ -286,6 +296,19 @@ public class NimbusReactiveJwtDecoderTests { verify(webClient).get(); } + // gh-8730 + @Test + public void withJwkSetUriWhenUsingCustomTypeHeaderThenRefuseOmittedType() { + WebClient webClient = mockJwkSetResponse(this.jwkSet); + NimbusReactiveJwtDecoder decoder = withJwkSetUri(this.jwkSetUri) + .webClient(webClient) + .jwtProcessorCustomizer(p -> p.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("JWS")))) + .build(); + assertThatCode(() -> decoder.decode(messageReadToken).block()) + .isInstanceOf(BadJwtException.class) + .hasRootCauseMessage("Required JOSE header \"typ\" (type) parameter is missing"); + } + @Test public void withPublicKeyWhenNullThenThrowsException() { assertThatThrownBy(() -> withPublicKey(null)) @@ -300,6 +323,13 @@ public class NimbusReactiveJwtDecoderTests { .isInstanceOf(IllegalStateException.class); } + @Test + public void buildWhenJwtProcessorCustomizerNullThenThrowsIllegalArgumentException() { + assertThatCode(() -> withPublicKey(key()).jwtProcessorCustomizer(null).build()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("jwtProcessorCustomizer cannot be null"); + } + @Test public void decodeWhenUsingPublicKeyThenSuccessfullyDecodes() throws Exception { NimbusReactiveJwtDecoder decoder = withPublicKey(key()).build(); @@ -325,12 +355,31 @@ public class NimbusReactiveJwtDecoderTests { .isInstanceOf(BadJwtException.class); } + // gh-8730 + @Test + public void withPublicKeyWhenUsingCustomTypeHeaderThenRefuseOmittedType() throws Exception { + NimbusReactiveJwtDecoder decoder = withPublicKey(key()) + .jwtProcessorCustomizer(p -> p.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("JWS")))) + .build(); + + AssertionsForClassTypes.assertThatCode(() -> decoder.decode(this.rsa256).block()) + .isInstanceOf(BadJwtException.class) + .hasRootCauseMessage("Required JOSE header \"typ\" (type) parameter is missing"); + } + @Test public void withJwkSourceWhenNullThenThrowsException() { assertThatCode(() -> withJwkSource(null)) .isInstanceOf(IllegalArgumentException.class); } + @Test + public void withJwkSourceWhenJwtProcessorCustomizerNullThenThrowsIllegalArgumentException() { + assertThatCode(() -> withJwkSource(jwt -> Flux.empty()).jwtProcessorCustomizer(null).build()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("jwtProcessorCustomizer cannot be null"); + } + @Test public void decodeWhenCustomJwkSourceResolutionThenDecodes() { NimbusReactiveJwtDecoder decoder = @@ -342,6 +391,18 @@ public class NimbusReactiveJwtDecoderTests { .isNotNull(); } + // gh-8730 + @Test + public void withJwkSourceWhenUsingCustomTypeHeaderThenRefuseOmittedType() { + NimbusReactiveJwtDecoder decoder = withJwkSource(jwt -> Flux.empty()) + .jwtProcessorCustomizer(p -> p.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("JWS")))) + .build(); + + assertThatCode(() -> decoder.decode(this.messageReadToken).block()) + .isInstanceOf(BadJwtException.class) + .hasRootCauseMessage("Required JOSE header \"typ\" (type) parameter is missing"); + } + @Test public void withSecretKeyWhenSecretKeyNullThenThrowsIllegalArgumentException() { assertThatThrownBy(() -> withSecretKey(null)) @@ -349,6 +410,14 @@ public class NimbusReactiveJwtDecoderTests { .hasMessage("secretKey cannot be null"); } + @Test + public void withSecretKeyWhenJwtProcessorCustomizerNullThenThrowsIllegalArgumentException() { + SecretKey secretKey = TestKeys.DEFAULT_SECRET_KEY; + assertThatThrownBy(() -> withSecretKey(secretKey).jwtProcessorCustomizer(null).build()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("jwtProcessorCustomizer cannot be null"); + } + @Test public void withSecretKeyWhenMacAlgorithmNullThenThrowsIllegalArgumentException() { SecretKey secretKey = TestKeys.DEFAULT_SECRET_KEY; @@ -372,6 +441,18 @@ public class NimbusReactiveJwtDecoderTests { assertThat(jwt.getSubject()).isEqualTo("test-subject"); } + // gh-8730 + @Test + public void withSecretKeyWhenUsingCustomTypeHeaderThenRefuseOmittedType() { + SecretKey secretKey = TestKeys.DEFAULT_SECRET_KEY; + NimbusReactiveJwtDecoder decoder = withSecretKey(secretKey) + .jwtProcessorCustomizer(p -> p.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("JWS")))) + .build(); + assertThatCode(() -> decoder.decode(messageReadToken).block()) + .isInstanceOf(BadJwtException.class) + .hasRootCauseMessage("Required JOSE header \"typ\" (type) parameter is missing"); + } + @Test public void decodeWhenSecretKeyAndAlgorithmMismatchThenThrowsJwtException() throws Exception { SecretKey secretKey = TestKeys.DEFAULT_SECRET_KEY;