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;