Преглед на файлове

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
Jan Oopkaup преди 5 години
родител
ревизия
d31fff11b3

+ 54 - 0
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<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
 		private Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
 		private RestOperations restOperations = new RestTemplate();
 		private RestOperations restOperations = new RestTemplate();
 		private Cache cache;
 		private Cache cache;
+		private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
 
 
 		private JwkSetUriJwtDecoderBuilder(String jwkSetUri) {
 		private JwkSetUriJwtDecoderBuilder(String jwkSetUri) {
 			Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty");
 			Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty");
 			this.jwkSetUri = jwkSetUri;
 			this.jwkSetUri = jwkSetUri;
+			this.jwtProcessorCustomizer = (processor) -> {};
 		}
 		}
 
 
 		/**
 		/**
@@ -282,6 +284,20 @@ public final class NimbusJwtDecoder implements JwtDecoder {
 			return this;
 			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<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer) {
+			Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
+			this.jwtProcessorCustomizer = jwtProcessorCustomizer;
+			return this;
+		}
+
 		JWSKeySelector<SecurityContext> jwsKeySelector(JWKSource<SecurityContext> jwkSource) {
 		JWSKeySelector<SecurityContext> jwsKeySelector(JWKSource<SecurityContext> jwkSource) {
 			if (this.signatureAlgorithms.isEmpty()) {
 			if (this.signatureAlgorithms.isEmpty()) {
 				return new JWSVerificationKeySelector<>(JWSAlgorithm.RS256, jwkSource);
 				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
 			// Spring Security validates the claim set independent from Nimbus
 			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
 			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
 
 
+			this.jwtProcessorCustomizer.accept(jwtProcessor);
+
 			return jwtProcessor;
 			return jwtProcessor;
 		}
 		}
 
 
@@ -414,11 +432,13 @@ public final class NimbusJwtDecoder implements JwtDecoder {
 	public static final class PublicKeyJwtDecoderBuilder {
 	public static final class PublicKeyJwtDecoderBuilder {
 		private JWSAlgorithm jwsAlgorithm;
 		private JWSAlgorithm jwsAlgorithm;
 		private RSAPublicKey key;
 		private RSAPublicKey key;
+		private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
 
 
 		private PublicKeyJwtDecoderBuilder(RSAPublicKey key) {
 		private PublicKeyJwtDecoderBuilder(RSAPublicKey key) {
 			Assert.notNull(key, "key cannot be null");
 			Assert.notNull(key, "key cannot be null");
 			this.jwsAlgorithm = JWSAlgorithm.RS256;
 			this.jwsAlgorithm = JWSAlgorithm.RS256;
 			this.key = key;
 			this.key = key;
+			this.jwtProcessorCustomizer = (processor) -> {};
 		}
 		}
 
 
 		/**
 		/**
@@ -437,6 +457,20 @@ public final class NimbusJwtDecoder implements JwtDecoder {
 			return this;
 			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<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer) {
+			Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
+			this.jwtProcessorCustomizer = jwtProcessorCustomizer;
+			return this;
+		}
+
 		JWTProcessor<SecurityContext> processor() {
 		JWTProcessor<SecurityContext> processor() {
 			if (!JWSAlgorithm.Family.RSA.contains(this.jwsAlgorithm)) {
 			if (!JWSAlgorithm.Family.RSA.contains(this.jwsAlgorithm)) {
 				throw new IllegalStateException("The provided key is of type RSA; " +
 				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
 			// Spring Security validates the claim set independent from Nimbus
 			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
 			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
 
 
+			this.jwtProcessorCustomizer.accept(jwtProcessor);
+
 			return jwtProcessor;
 			return jwtProcessor;
 		}
 		}
 
 
@@ -471,10 +507,12 @@ public final class NimbusJwtDecoder implements JwtDecoder {
 	public static final class SecretKeyJwtDecoderBuilder {
 	public static final class SecretKeyJwtDecoderBuilder {
 		private final SecretKey secretKey;
 		private final SecretKey secretKey;
 		private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;
 		private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;
+		private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
 
 
 		private SecretKeyJwtDecoderBuilder(SecretKey secretKey) {
 		private SecretKeyJwtDecoderBuilder(SecretKey secretKey) {
 			Assert.notNull(secretKey, "secretKey cannot be null");
 			Assert.notNull(secretKey, "secretKey cannot be null");
 			this.secretKey = secretKey;
 			this.secretKey = secretKey;
+			this.jwtProcessorCustomizer = (processor) -> {};
 		}
 		}
 
 
 		/**
 		/**
@@ -494,6 +532,20 @@ public final class NimbusJwtDecoder implements JwtDecoder {
 			return this;
 			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<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer) {
+			Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
+			this.jwtProcessorCustomizer = jwtProcessorCustomizer;
+			return this;
+		}
+
 		/**
 		/**
 		 * Build the configured {@link NimbusJwtDecoder}.
 		 * Build the configured {@link NimbusJwtDecoder}.
 		 *
 		 *
@@ -512,6 +564,8 @@ public final class NimbusJwtDecoder implements JwtDecoder {
 			// Spring Security validates the claim set independent from Nimbus
 			// Spring Security validates the claim set independent from Nimbus
 			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
 			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
 
 
+			this.jwtProcessorCustomizer.accept(jwtProcessor);
+
 			return jwtProcessor;
 			return jwtProcessor;
 		}
 		}
 	}
 	}

+ 73 - 0
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.JWTParser;
 import com.nimbusds.jwt.PlainJWT;
 import com.nimbusds.jwt.PlainJWT;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
 import com.nimbusds.jwt.proc.DefaultJWTProcessor;
 import com.nimbusds.jwt.proc.DefaultJWTProcessor;
 import com.nimbusds.jwt.proc.JWTProcessor;
 import com.nimbusds.jwt.proc.JWTProcessor;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Flux;
@@ -245,10 +246,12 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
 		private final String jwkSetUri;
 		private final String jwkSetUri;
 		private Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
 		private Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
 		private WebClient webClient = WebClient.create();
 		private WebClient webClient = WebClient.create();
+		private Consumer<ConfigurableJWTProcessor<JWKSecurityContext>> jwtProcessorCustomizer;
 
 
 		private JwkSetUriReactiveJwtDecoderBuilder(String jwkSetUri) {
 		private JwkSetUriReactiveJwtDecoderBuilder(String jwkSetUri) {
 			Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty");
 			Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty");
 			this.jwkSetUri = jwkSetUri;
 			this.jwkSetUri = jwkSetUri;
+			this.jwtProcessorCustomizer = (processor) -> {};
 		}
 		}
 
 
 		/**
 		/**
@@ -294,6 +297,20 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
 			return this;
 			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<ConfigurableJWTProcessor<JWKSecurityContext>> jwtProcessorCustomizer) {
+			Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
+			this.jwtProcessorCustomizer = jwtProcessorCustomizer;
+			return this;
+		}
+
 		/**
 		/**
 		 * Build the configured {@link NimbusReactiveJwtDecoder}.
 		 * Build the configured {@link NimbusReactiveJwtDecoder}.
 		 *
 		 *
@@ -323,6 +340,8 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
 			jwtProcessor.setJWSKeySelector(jwsKeySelector);
 			jwtProcessor.setJWSKeySelector(jwsKeySelector);
 			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {});
 			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {});
 
 
+			this.jwtProcessorCustomizer.accept(jwtProcessor);
+
 			ReactiveRemoteJWKSource source = new ReactiveRemoteJWKSource(this.jwkSetUri);
 			ReactiveRemoteJWKSource source = new ReactiveRemoteJWKSource(this.jwkSetUri);
 			source.setWebClient(this.webClient);
 			source.setWebClient(this.webClient);
 
 
@@ -360,11 +379,13 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
 	public static final class PublicKeyReactiveJwtDecoderBuilder {
 	public static final class PublicKeyReactiveJwtDecoderBuilder {
 		private final RSAPublicKey key;
 		private final RSAPublicKey key;
 		private JWSAlgorithm jwsAlgorithm;
 		private JWSAlgorithm jwsAlgorithm;
+		private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
 
 
 		private PublicKeyReactiveJwtDecoderBuilder(RSAPublicKey key) {
 		private PublicKeyReactiveJwtDecoderBuilder(RSAPublicKey key) {
 			Assert.notNull(key, "key cannot be null");
 			Assert.notNull(key, "key cannot be null");
 			this.key = key;
 			this.key = key;
 			this.jwsAlgorithm = JWSAlgorithm.RS256;
 			this.jwsAlgorithm = JWSAlgorithm.RS256;
+			this.jwtProcessorCustomizer = (processor) -> {};
 		}
 		}
 
 
 		/**
 		/**
@@ -382,6 +403,20 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
 			return this;
 			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<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer) {
+			Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
+			this.jwtProcessorCustomizer = jwtProcessorCustomizer;
+			return this;
+		}
+
 		/**
 		/**
 		 * Build the configured {@link NimbusReactiveJwtDecoder}.
 		 * Build the configured {@link NimbusReactiveJwtDecoder}.
 		 *
 		 *
@@ -406,6 +441,8 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
 			// Spring Security validates the claim set independent from Nimbus
 			// Spring Security validates the claim set independent from Nimbus
 			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
 			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
 
 
+			this.jwtProcessorCustomizer.accept(jwtProcessor);
+
 			return jwt -> Mono.just(createClaimsSet(jwtProcessor, jwt, null));
 			return jwt -> Mono.just(createClaimsSet(jwtProcessor, jwt, null));
 		}
 		}
 	}
 	}
@@ -418,10 +455,12 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
 	public static final class SecretKeyReactiveJwtDecoderBuilder {
 	public static final class SecretKeyReactiveJwtDecoderBuilder {
 		private final SecretKey secretKey;
 		private final SecretKey secretKey;
 		private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;
 		private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;
+		private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
 
 
 		private SecretKeyReactiveJwtDecoderBuilder(SecretKey secretKey) {
 		private SecretKeyReactiveJwtDecoderBuilder(SecretKey secretKey) {
 			Assert.notNull(secretKey, "secretKey cannot be null");
 			Assert.notNull(secretKey, "secretKey cannot be null");
 			this.secretKey = secretKey;
 			this.secretKey = secretKey;
+			this.jwtProcessorCustomizer = (processor) -> {};
 		}
 		}
 
 
 		/**
 		/**
@@ -441,6 +480,20 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
 			return this;
 			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<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer) {
+			Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
+			this.jwtProcessorCustomizer = jwtProcessorCustomizer;
+			return this;
+		}
+
 		/**
 		/**
 		 * Build the configured {@link NimbusReactiveJwtDecoder}.
 		 * Build the configured {@link NimbusReactiveJwtDecoder}.
 		 *
 		 *
@@ -459,6 +512,8 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
 			// Spring Security validates the claim set independent from Nimbus
 			// Spring Security validates the claim set independent from Nimbus
 			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
 			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
 
 
+			this.jwtProcessorCustomizer.accept(jwtProcessor);
+
 			return jwt -> Mono.just(createClaimsSet(jwtProcessor, jwt, null));
 			return jwt -> Mono.just(createClaimsSet(jwtProcessor, jwt, null));
 		}
 		}
 	}
 	}
@@ -471,10 +526,12 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
 	public static final class JwkSourceReactiveJwtDecoderBuilder {
 	public static final class JwkSourceReactiveJwtDecoderBuilder {
 		private final Function<SignedJWT, Flux<JWK>> jwkSource;
 		private final Function<SignedJWT, Flux<JWK>> jwkSource;
 		private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256;
 		private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256;
+		private Consumer<ConfigurableJWTProcessor<JWKSecurityContext>> jwtProcessorCustomizer;
 
 
 		private JwkSourceReactiveJwtDecoderBuilder(Function<SignedJWT, Flux<JWK>> jwkSource) {
 		private JwkSourceReactiveJwtDecoderBuilder(Function<SignedJWT, Flux<JWK>> jwkSource) {
 			Assert.notNull(jwkSource, "jwkSource cannot be null");
 			Assert.notNull(jwkSource, "jwkSource cannot be null");
 			this.jwkSource = jwkSource;
 			this.jwkSource = jwkSource;
+			this.jwtProcessorCustomizer = (processor) -> {};
 		}
 		}
 
 
 		/**
 		/**
@@ -490,6 +547,20 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
 			return this;
 			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<ConfigurableJWTProcessor<JWKSecurityContext>> jwtProcessorCustomizer) {
+			Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
+			this.jwtProcessorCustomizer = jwtProcessorCustomizer;
+			return this;
+		}
+
 		/**
 		/**
 		 * Build the configured {@link NimbusReactiveJwtDecoder}.
 		 * Build the configured {@link NimbusReactiveJwtDecoder}.
 		 *
 		 *
@@ -507,6 +578,8 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
 			jwtProcessor.setJWSKeySelector(jwsKeySelector);
 			jwtProcessor.setJWSKeySelector(jwsKeySelector);
 			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {});
 			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {});
 
 
+			this.jwtProcessorCustomizer.accept(jwtProcessor);
+
 			return jwt -> {
 			return jwt -> {
 				if (jwt instanceof SignedJWT) {
 				if (jwt instanceof SignedJWT) {
 					return this.jwkSource.apply((SignedJWT) jwt)
 					return this.jwkSource.apply((SignedJWT) jwt)

+ 72 - 0
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 java.util.concurrent.Callable;
 import javax.crypto.SecretKey;
 import javax.crypto.SecretKey;
 
 
+import com.nimbusds.jose.JOSEObjectType;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.JWSHeader;
 import com.nimbusds.jose.JWSHeader;
 import com.nimbusds.jose.JWSSigner;
 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.crypto.RSASSASigner;
 import com.nimbusds.jose.jwk.source.JWKSource;
 import com.nimbusds.jose.jwk.source.JWKSource;
 import com.nimbusds.jose.proc.BadJOSEException;
 import com.nimbusds.jose.proc.BadJOSEException;
+import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier;
 import com.nimbusds.jose.proc.JWSKeySelector;
 import com.nimbusds.jose.proc.JWSKeySelector;
 import com.nimbusds.jose.proc.JWSVerificationKeySelector;
 import com.nimbusds.jose.proc.JWSVerificationKeySelector;
 import com.nimbusds.jose.proc.SecurityContext;
 import com.nimbusds.jose.proc.SecurityContext;
@@ -346,6 +348,30 @@ public class NimbusJwtDecoderTests {
 				.isInstanceOf(BadJwtException.class);
 				.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
 	@Test
 	public void withSecretKeyWhenNullThenThrowsIllegalArgumentException() {
 	public void withSecretKeyWhenNullThenThrowsIllegalArgumentException() {
 		assertThatThrownBy(() -> withSecretKey(null))
 		assertThatThrownBy(() -> withSecretKey(null))
@@ -407,6 +433,30 @@ public class NimbusJwtDecoderTests {
 				.isEqualTo("test-subject");
 				.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
 	@Test
 	public void jwsKeySelectorWhenNoAlgorithmThenReturnsRS256Selector() {
 	public void jwsKeySelectorWhenNoAlgorithmThenReturnsRS256Selector() {
 		JWKSource<SecurityContext> jwkSource = mock(JWKSource.class);
 		JWKSource<SecurityContext> jwkSource = mock(JWKSource.class);
@@ -524,6 +574,28 @@ public class NimbusJwtDecoderTests {
 				.hasMessageContaining("An error occurred while attempting to decode the Jwt");
 				.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 {
 	private RSAPublicKey key() throws InvalidKeySpecException {
 		byte[] decoded = Base64.getDecoder().decode(VERIFY_KEY.getBytes());
 		byte[] decoded = Base64.getDecoder().decode(VERIFY_KEY.getBytes());
 		EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
 		EncodedKeySpec spec = new X509EncodedKeySpec(decoded);

+ 81 - 0
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 java.util.Map;
 import javax.crypto.SecretKey;
 import javax.crypto.SecretKey;
 
 
+import com.nimbusds.jose.JOSEObjectType;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.JWSHeader;
 import com.nimbusds.jose.JWSHeader;
 import com.nimbusds.jose.JWSSigner;
 import com.nimbusds.jose.JWSSigner;
 import com.nimbusds.jose.crypto.MACSigner;
 import com.nimbusds.jose.crypto.MACSigner;
 import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jose.jwk.source.JWKSource;
 import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier;
 import com.nimbusds.jose.proc.JWKSecurityContext;
 import com.nimbusds.jose.proc.JWKSecurityContext;
 import com.nimbusds.jose.proc.JWSKeySelector;
 import com.nimbusds.jose.proc.JWSKeySelector;
 import com.nimbusds.jose.proc.JWSVerificationKeySelector;
 import com.nimbusds.jose.proc.JWSVerificationKeySelector;
@@ -44,6 +46,7 @@ import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.jwt.SignedJWT;
 import okhttp3.mockwebserver.MockResponse;
 import okhttp3.mockwebserver.MockResponse;
 import okhttp3.mockwebserver.MockWebServer;
 import okhttp3.mockwebserver.MockWebServer;
+import org.assertj.core.api.AssertionsForClassTypes;
 import org.junit.After;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.BeforeClass;
@@ -269,6 +272,13 @@ public class NimbusReactiveJwtDecoderTests {
 		assertThatCode(() -> builder.jwsAlgorithm(null)).isInstanceOf(IllegalArgumentException.class);
 		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
 	@Test
 	public void restOperationsWhenNullThenThrowsException() {
 	public void restOperationsWhenNullThenThrowsException() {
 		NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder builder = withJwkSetUri(this.jwkSetUri);
 		NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder builder = withJwkSetUri(this.jwkSetUri);
@@ -286,6 +296,19 @@ public class NimbusReactiveJwtDecoderTests {
 		verify(webClient).get();
 		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
 	@Test
 	public void withPublicKeyWhenNullThenThrowsException() {
 	public void withPublicKeyWhenNullThenThrowsException() {
 		assertThatThrownBy(() -> withPublicKey(null))
 		assertThatThrownBy(() -> withPublicKey(null))
@@ -300,6 +323,13 @@ public class NimbusReactiveJwtDecoderTests {
 				.isInstanceOf(IllegalStateException.class);
 				.isInstanceOf(IllegalStateException.class);
 	}
 	}
 
 
+	@Test
+	public void buildWhenJwtProcessorCustomizerNullThenThrowsIllegalArgumentException() {
+		assertThatCode(() -> withPublicKey(key()).jwtProcessorCustomizer(null).build())
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("jwtProcessorCustomizer cannot be null");
+	}
+
 	@Test
 	@Test
 	public void decodeWhenUsingPublicKeyThenSuccessfullyDecodes() throws Exception {
 	public void decodeWhenUsingPublicKeyThenSuccessfullyDecodes() throws Exception {
 		NimbusReactiveJwtDecoder decoder = withPublicKey(key()).build();
 		NimbusReactiveJwtDecoder decoder = withPublicKey(key()).build();
@@ -325,12 +355,31 @@ public class NimbusReactiveJwtDecoderTests {
 				.isInstanceOf(BadJwtException.class);
 				.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
 	@Test
 	public void withJwkSourceWhenNullThenThrowsException() {
 	public void withJwkSourceWhenNullThenThrowsException() {
 		assertThatCode(() -> withJwkSource(null))
 		assertThatCode(() -> withJwkSource(null))
 				.isInstanceOf(IllegalArgumentException.class);
 				.isInstanceOf(IllegalArgumentException.class);
 	}
 	}
 
 
+	@Test
+	public void withJwkSourceWhenJwtProcessorCustomizerNullThenThrowsIllegalArgumentException() {
+		assertThatCode(() -> withJwkSource(jwt -> Flux.empty()).jwtProcessorCustomizer(null).build())
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("jwtProcessorCustomizer cannot be null");
+	}
+
 	@Test
 	@Test
 	public void decodeWhenCustomJwkSourceResolutionThenDecodes() {
 	public void decodeWhenCustomJwkSourceResolutionThenDecodes() {
 		NimbusReactiveJwtDecoder decoder =
 		NimbusReactiveJwtDecoder decoder =
@@ -342,6 +391,18 @@ public class NimbusReactiveJwtDecoderTests {
 				.isNotNull();
 				.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
 	@Test
 	public void withSecretKeyWhenSecretKeyNullThenThrowsIllegalArgumentException() {
 	public void withSecretKeyWhenSecretKeyNullThenThrowsIllegalArgumentException() {
 		assertThatThrownBy(() -> withSecretKey(null))
 		assertThatThrownBy(() -> withSecretKey(null))
@@ -349,6 +410,14 @@ public class NimbusReactiveJwtDecoderTests {
 				.hasMessage("secretKey cannot be null");
 				.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
 	@Test
 	public void withSecretKeyWhenMacAlgorithmNullThenThrowsIllegalArgumentException() {
 	public void withSecretKeyWhenMacAlgorithmNullThenThrowsIllegalArgumentException() {
 		SecretKey secretKey = TestKeys.DEFAULT_SECRET_KEY;
 		SecretKey secretKey = TestKeys.DEFAULT_SECRET_KEY;
@@ -372,6 +441,18 @@ public class NimbusReactiveJwtDecoderTests {
 		assertThat(jwt.getSubject()).isEqualTo("test-subject");
 		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
 	@Test
 	public void decodeWhenSecretKeyAndAlgorithmMismatchThenThrowsJwtException() throws Exception {
 	public void decodeWhenSecretKeyAndAlgorithmMismatchThenThrowsJwtException() throws Exception {
 		SecretKey secretKey = TestKeys.DEFAULT_SECRET_KEY;
 		SecretKey secretKey = TestKeys.DEFAULT_SECRET_KEY;