Эх сурвалжийг харах

Merge branch 0.4.x into main

The following commits are merged using the default merge strategy.

72804be45bed24b7c3ee4c0fbe3280d178ba13da Extract OIDC client configuration implementation
b1b2bc438fb2e9c9b298d92f08ef0ca162ec3130 Update OAuth 2.1 spec link in README.adoc
8c2b095195173a1ce7dd9d4b6f7965f800914f0c Extract JwtDecoderFactory from JwtClientAssertionAuthenticationProvider
Joe Grandja 2 жил өмнө
parent
commit
feec9a64a4
15 өөрчлөгдсөн 1182 нэмэгдсэн , 623 устгасан
  1. 1 1
      README.adoc
  2. 2 1
      docs/src/docs/asciidoc/protocol-endpoints.adoc
  3. 18 138
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProvider.java
  4. 198 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionDecoderFactory.java
  5. 7 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java
  6. 147 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java
  7. 99 172
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java
  8. 1 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java
  9. 89 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationConverter.java
  10. 16 27
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java
  11. 78 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcClientRegistrationAuthenticationConverter.java
  12. 7 79
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProviderTests.java
  13. 112 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionDecoderFactoryTests.java
  14. 400 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProviderTests.java
  15. 7 205
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java

+ 1 - 1
README.adoc

@@ -4,7 +4,7 @@ image:https://github.com/spring-projects/spring-authorization-server/workflows/C
 
 = Spring Authorization Server
 
-The Spring Authorization Server project, led by the https://spring.io/projects/spring-security/[Spring Security] team, is focused on delivering https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-1.1[OAuth 2.1 Authorization Server] support to the Spring community.
+The Spring Authorization Server project, led by the https://spring.io/projects/spring-security/[Spring Security] team, is focused on delivering https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-06#section-1.1[OAuth 2.1 Authorization Server] support to the Spring community.
 
 This project replaces the Authorization Server support provided by https://spring.io/projects/spring-security-oauth/[Spring Security OAuth].
 

+ 2 - 1
docs/src/docs/asciidoc/protocol-endpoints.adoc

@@ -369,7 +369,8 @@ The OpenID Connect 1.0 Client Registration endpoint is disabled by default becau
 
 `OidcClientRegistrationEndpointFilter` is configured with the following defaults:
 
-* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OidcClientRegistrationAuthenticationProvider`.
+* `*AuthenticationConverter*` -- An `OidcClientRegistrationAuthenticationConverter`.
+* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OidcClientRegistrationAuthenticationProvider` and `OidcClientConfigurationAuthenticationProvider`.
 
 The OpenID Connect 1.0 Client Registration endpoint is an https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration[OAuth2 protected resource], which *REQUIRES* an access token to be sent as a bearer token in the Client Registration (or Client Read) request.
 

+ 18 - 138
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProvider.java

@@ -15,53 +15,27 @@
  */
 package org.springframework.security.oauth2.server.authorization.authentication;
 
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Predicate;
-
-import javax.crypto.spec.SecretKeySpec;
-
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
-import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
-import org.springframework.security.oauth2.core.OAuth2TokenValidator;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
-import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
-import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
 import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
 import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.oauth2.jwt.JwtClaimNames;
-import org.springframework.security.oauth2.jwt.JwtClaimValidator;
 import org.springframework.security.oauth2.jwt.JwtDecoder;
 import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
 import org.springframework.security.oauth2.jwt.JwtException;
-import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
-import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
-import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext;
-import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
-import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
 import org.springframework.util.Assert;
-import org.springframework.util.CollectionUtils;
-import org.springframework.util.StringUtils;
-import org.springframework.web.util.UriComponentsBuilder;
 
 /**
  * An {@link AuthenticationProvider} implementation used for OAuth 2.0 Client Authentication,
- * which authenticates the (JWT) {@link OAuth2ParameterNames#CLIENT_ASSERTION client_assertion} parameter.
+ * which authenticates the {@link Jwt} {@link OAuth2ParameterNames#CLIENT_ASSERTION client_assertion} parameter.
  *
  * @author Rafal Lewczuk
  * @author Joe Grandja
@@ -70,6 +44,7 @@ import org.springframework.web.util.UriComponentsBuilder;
  * @see OAuth2ClientAuthenticationToken
  * @see RegisteredClientRepository
  * @see OAuth2AuthorizationService
+ * @see JwtClientAssertionDecoderFactory
  */
 public final class JwtClientAssertionAuthenticationProvider implements AuthenticationProvider {
 	private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1";
@@ -77,7 +52,7 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic
 			new ClientAuthenticationMethod("urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
 	private final RegisteredClientRepository registeredClientRepository;
 	private final CodeVerifierAuthenticator codeVerifierAuthenticator;
-	private final JwtClientAssertionDecoderFactory jwtClientAssertionDecoderFactory;
+	private JwtDecoderFactory<RegisteredClient> jwtDecoderFactory;
 
 	/**
 	 * Constructs a {@code JwtClientAssertionAuthenticationProvider} using the provided parameters.
@@ -91,7 +66,7 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic
 		Assert.notNull(authorizationService, "authorizationService cannot be null");
 		this.registeredClientRepository = registeredClientRepository;
 		this.codeVerifierAuthenticator = new CodeVerifierAuthenticator(authorizationService);
-		this.jwtClientAssertionDecoderFactory = new JwtClientAssertionDecoderFactory();
+		this.jwtDecoderFactory = new JwtClientAssertionDecoderFactory();
 	}
 
 	@Override
@@ -119,7 +94,7 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic
 		}
 
 		Jwt jwtAssertion = null;
-		JwtDecoder jwtDecoder = this.jwtClientAssertionDecoderFactory.createDecoder(registeredClient);
+		JwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(registeredClient);
 		try {
 			jwtAssertion = jwtDecoder.decode(clientAuthentication.getCredentials().toString());
 		} catch (JwtException ex) {
@@ -142,6 +117,19 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic
 		return OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication);
 	}
 
+	/**
+	 * Sets the {@link JwtDecoderFactory} that provides a {@link JwtDecoder} for the specified {@link RegisteredClient}
+	 * and is used for authenticating a {@link Jwt} Bearer Token during OAuth 2.0 Client Authentication.
+	 * The default factory is {@link JwtClientAssertionDecoderFactory}.
+	 *
+	 * @param jwtDecoderFactory the {@link JwtDecoderFactory} that provides a {@link JwtDecoder} for the specified {@link RegisteredClient}
+	 * @since 0.4.0
+	 */
+	public void setJwtDecoderFactory(JwtDecoderFactory<RegisteredClient> jwtDecoderFactory) {
+		Assert.notNull(jwtDecoderFactory, "jwtDecoderFactory cannot be null");
+		this.jwtDecoderFactory = jwtDecoderFactory;
+	}
+
 	private static void throwInvalidClient(String parameterName) {
 		throwInvalidClient(parameterName, null);
 	}
@@ -155,112 +143,4 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic
 		throw new OAuth2AuthenticationException(error, error.toString(), cause);
 	}
 
-	private static class JwtClientAssertionDecoderFactory implements JwtDecoderFactory<RegisteredClient> {
-		private static final String JWT_CLIENT_AUTHENTICATION_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc7523#section-3";
-
-		private static final Map<JwsAlgorithm, String> JCA_ALGORITHM_MAPPINGS;
-
-		static {
-			Map<JwsAlgorithm, String> mappings = new HashMap<>();
-			mappings.put(MacAlgorithm.HS256, "HmacSHA256");
-			mappings.put(MacAlgorithm.HS384, "HmacSHA384");
-			mappings.put(MacAlgorithm.HS512, "HmacSHA512");
-			JCA_ALGORITHM_MAPPINGS = Collections.unmodifiableMap(mappings);
-		}
-
-		private final Map<String, JwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
-
-		@Override
-		public JwtDecoder createDecoder(RegisteredClient registeredClient) {
-			Assert.notNull(registeredClient, "registeredClient cannot be null");
-			return this.jwtDecoders.computeIfAbsent(registeredClient.getId(), (key) -> {
-				NimbusJwtDecoder jwtDecoder = buildDecoder(registeredClient);
-				jwtDecoder.setJwtValidator(createJwtValidator(registeredClient));
-				return jwtDecoder;
-			});
-		}
-
-		private static NimbusJwtDecoder buildDecoder(RegisteredClient registeredClient) {
-			JwsAlgorithm jwsAlgorithm = registeredClient.getClientSettings().getTokenEndpointAuthenticationSigningAlgorithm();
-			if (jwsAlgorithm instanceof SignatureAlgorithm) {
-				String jwkSetUrl = registeredClient.getClientSettings().getJwkSetUrl();
-				if (!StringUtils.hasText(jwkSetUrl)) {
-					OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
-							"Failed to find a Signature Verifier for Client: '"
-									+ registeredClient.getId()
-									+ "'. Check to ensure you have configured the JWK Set URL.",
-							JWT_CLIENT_AUTHENTICATION_ERROR_URI);
-					throw new OAuth2AuthenticationException(oauth2Error);
-				}
-				return NimbusJwtDecoder.withJwkSetUri(jwkSetUrl).jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm).build();
-			}
-			if (jwsAlgorithm instanceof MacAlgorithm) {
-				String clientSecret = registeredClient.getClientSecret();
-				if (!StringUtils.hasText(clientSecret)) {
-					OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
-							"Failed to find a Signature Verifier for Client: '"
-									+ registeredClient.getId()
-									+ "'. Check to ensure you have configured the client secret.",
-							JWT_CLIENT_AUTHENTICATION_ERROR_URI);
-					throw new OAuth2AuthenticationException(oauth2Error);
-				}
-				SecretKeySpec secretKeySpec = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8),
-						JCA_ALGORITHM_MAPPINGS.get(jwsAlgorithm));
-				return NimbusJwtDecoder.withSecretKey(secretKeySpec).macAlgorithm((MacAlgorithm) jwsAlgorithm).build();
-			}
-			OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
-					"Failed to find a Signature Verifier for Client: '"
-							+ registeredClient.getId()
-							+ "'. Check to ensure you have configured a valid JWS Algorithm: '" + jwsAlgorithm + "'.",
-					JWT_CLIENT_AUTHENTICATION_ERROR_URI);
-			throw new OAuth2AuthenticationException(oauth2Error);
-		}
-
-		private static OAuth2TokenValidator<Jwt> createJwtValidator(RegisteredClient registeredClient) {
-			String clientId = registeredClient.getClientId();
-			return new DelegatingOAuth2TokenValidator<>(
-					new JwtClaimValidator<>(JwtClaimNames.ISS, clientId::equals),
-					new JwtClaimValidator<>(JwtClaimNames.SUB, clientId::equals),
-					new JwtClaimValidator<>(JwtClaimNames.AUD, containsAudience()),
-					new JwtClaimValidator<>(JwtClaimNames.EXP, Objects::nonNull),
-					new JwtTimestampValidator()
-			);
-		}
-
-		private static Predicate<List<String>> containsAudience() {
-			return (audienceClaim) -> {
-				if (CollectionUtils.isEmpty(audienceClaim)) {
-					return false;
-				}
-				List<String> audienceList = getAudience();
-				for (String audience : audienceClaim) {
-					if (audienceList.contains(audience)) {
-						return true;
-					}
-				}
-				return false;
-			};
-		}
-
-		private static List<String> getAudience() {
-			AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
-			if (!StringUtils.hasText(authorizationServerContext.getIssuer())) {
-				return Collections.emptyList();
-			}
-
-			AuthorizationServerSettings authorizationServerSettings = authorizationServerContext.getAuthorizationServerSettings();
-			List<String> audience = new ArrayList<>();
-			audience.add(authorizationServerContext.getIssuer());
-			audience.add(asUrl(authorizationServerContext.getIssuer(), authorizationServerSettings.getTokenEndpoint()));
-			audience.add(asUrl(authorizationServerContext.getIssuer(), authorizationServerSettings.getTokenIntrospectionEndpoint()));
-			audience.add(asUrl(authorizationServerContext.getIssuer(), authorizationServerSettings.getTokenRevocationEndpoint()));
-			return audience;
-		}
-
-		private static String asUrl(String issuer, String endpoint) {
-			return UriComponentsBuilder.fromUriString(issuer).path(endpoint).build().toUriString();
-		}
-
-	}
-
 }

+ 198 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionDecoderFactory.java

@@ -0,0 +1,198 @@
+/*
+ * Copyright 2020-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.oauth2.server.authorization.authentication;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.crypto.spec.SecretKeySpec;
+
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
+import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
+import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
+import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtClaimNames;
+import org.springframework.security.oauth2.jwt.JwtClaimValidator;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
+import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext;
+import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
+import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.util.UriComponentsBuilder;
+
+/**
+ * A {@link JwtDecoderFactory factory} that provides a {@link JwtDecoder} for the specified {@link RegisteredClient}
+ * and is used for authenticating a {@link Jwt} Bearer Token during OAuth 2.0 Client Authentication.
+ *
+ * @author Rafal Lewczuk
+ * @author Joe Grandja
+ * @since 0.4.0
+ * @see JwtDecoderFactory
+ * @see RegisteredClient
+ * @see OAuth2TokenValidator
+ * @see JwtClientAssertionAuthenticationProvider
+ * @see ClientAuthenticationMethod#PRIVATE_KEY_JWT
+ * @see ClientAuthenticationMethod#CLIENT_SECRET_JWT
+ */
+public final class JwtClientAssertionDecoderFactory implements JwtDecoderFactory<RegisteredClient> {
+
+	/**
+	 * The default {@code OAuth2TokenValidator<Jwt>} factory that validates the {@link JwtClaimNames#ISS iss},
+	 * {@link JwtClaimNames#SUB sub}, {@link JwtClaimNames#AUD aud}, {@link JwtClaimNames#EXP exp} and
+	 * {@link JwtClaimNames#NBF nbf} claims of the {@link Jwt} for the specified {@link RegisteredClient}.
+	 */
+	public static final Function<RegisteredClient, OAuth2TokenValidator<Jwt>> DEFAULT_JWT_VALIDATOR_FACTORY = defaultJwtValidatorFactory();
+
+	private static final String JWT_CLIENT_AUTHENTICATION_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc7523#section-3";
+	private static final Map<JwsAlgorithm, String> JCA_ALGORITHM_MAPPINGS;
+
+	static {
+		Map<JwsAlgorithm, String> mappings = new HashMap<>();
+		mappings.put(MacAlgorithm.HS256, "HmacSHA256");
+		mappings.put(MacAlgorithm.HS384, "HmacSHA384");
+		mappings.put(MacAlgorithm.HS512, "HmacSHA512");
+		JCA_ALGORITHM_MAPPINGS = Collections.unmodifiableMap(mappings);
+	}
+
+	private final Map<String, JwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
+	private Function<RegisteredClient, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = DEFAULT_JWT_VALIDATOR_FACTORY;
+
+	@Override
+	public JwtDecoder createDecoder(RegisteredClient registeredClient) {
+		Assert.notNull(registeredClient, "registeredClient cannot be null");
+		return this.jwtDecoders.computeIfAbsent(registeredClient.getId(), (key) -> {
+			NimbusJwtDecoder jwtDecoder = buildDecoder(registeredClient);
+			jwtDecoder.setJwtValidator(this.jwtValidatorFactory.apply(registeredClient));
+			return jwtDecoder;
+		});
+	}
+
+	/**
+	 * Sets the factory that provides an {@link OAuth2TokenValidator}
+	 * for the specified {@link RegisteredClient} and is used by the {@link JwtDecoder}.
+	 * The default {@code OAuth2TokenValidator<Jwt>} factory is {@link #DEFAULT_JWT_VALIDATOR_FACTORY}.
+	 *
+	 * @param jwtValidatorFactory the factory that provides an {@link OAuth2TokenValidator} for the specified {@link RegisteredClient}
+	 */
+	public void setJwtValidatorFactory(Function<RegisteredClient, OAuth2TokenValidator<Jwt>> jwtValidatorFactory) {
+		Assert.notNull(jwtValidatorFactory, "jwtValidatorFactory cannot be null");
+		this.jwtValidatorFactory = jwtValidatorFactory;
+	}
+
+	private static NimbusJwtDecoder buildDecoder(RegisteredClient registeredClient) {
+		JwsAlgorithm jwsAlgorithm = registeredClient.getClientSettings().getTokenEndpointAuthenticationSigningAlgorithm();
+		if (jwsAlgorithm instanceof SignatureAlgorithm) {
+			String jwkSetUrl = registeredClient.getClientSettings().getJwkSetUrl();
+			if (!StringUtils.hasText(jwkSetUrl)) {
+				OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
+						"Failed to find a Signature Verifier for Client: '"
+								+ registeredClient.getId()
+								+ "'. Check to ensure you have configured the JWK Set URL.",
+						JWT_CLIENT_AUTHENTICATION_ERROR_URI);
+				throw new OAuth2AuthenticationException(oauth2Error);
+			}
+			return NimbusJwtDecoder.withJwkSetUri(jwkSetUrl).jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm).build();
+		}
+		if (jwsAlgorithm instanceof MacAlgorithm) {
+			String clientSecret = registeredClient.getClientSecret();
+			if (!StringUtils.hasText(clientSecret)) {
+				OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
+						"Failed to find a Signature Verifier for Client: '"
+								+ registeredClient.getId()
+								+ "'. Check to ensure you have configured the client secret.",
+						JWT_CLIENT_AUTHENTICATION_ERROR_URI);
+				throw new OAuth2AuthenticationException(oauth2Error);
+			}
+			SecretKeySpec secretKeySpec = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8),
+					JCA_ALGORITHM_MAPPINGS.get(jwsAlgorithm));
+			return NimbusJwtDecoder.withSecretKey(secretKeySpec).macAlgorithm((MacAlgorithm) jwsAlgorithm).build();
+		}
+		OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
+				"Failed to find a Signature Verifier for Client: '"
+						+ registeredClient.getId()
+						+ "'. Check to ensure you have configured a valid JWS Algorithm: '" + jwsAlgorithm + "'.",
+				JWT_CLIENT_AUTHENTICATION_ERROR_URI);
+		throw new OAuth2AuthenticationException(oauth2Error);
+	}
+
+	private static Function<RegisteredClient, OAuth2TokenValidator<Jwt>> defaultJwtValidatorFactory() {
+		return (registeredClient) -> {
+			String clientId = registeredClient.getClientId();
+			return new DelegatingOAuth2TokenValidator<>(
+					new JwtClaimValidator<>(JwtClaimNames.ISS, clientId::equals),
+					new JwtClaimValidator<>(JwtClaimNames.SUB, clientId::equals),
+					new JwtClaimValidator<>(JwtClaimNames.AUD, containsAudience()),
+					new JwtClaimValidator<>(JwtClaimNames.EXP, Objects::nonNull),
+					new JwtTimestampValidator()
+			);
+		};
+	}
+
+	private static Predicate<List<String>> containsAudience() {
+		return (audienceClaim) -> {
+			if (CollectionUtils.isEmpty(audienceClaim)) {
+				return false;
+			}
+			List<String> audienceList = getAudience();
+			for (String audience : audienceClaim) {
+				if (audienceList.contains(audience)) {
+					return true;
+				}
+			}
+			return false;
+		};
+	}
+
+	private static List<String> getAudience() {
+		AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
+		if (!StringUtils.hasText(authorizationServerContext.getIssuer())) {
+			return Collections.emptyList();
+		}
+
+		AuthorizationServerSettings authorizationServerSettings = authorizationServerContext.getAuthorizationServerSettings();
+		List<String> audience = new ArrayList<>();
+		audience.add(authorizationServerContext.getIssuer());
+		audience.add(asUrl(authorizationServerContext.getIssuer(), authorizationServerSettings.getTokenEndpoint()));
+		audience.add(asUrl(authorizationServerContext.getIssuer(), authorizationServerSettings.getTokenIntrospectionEndpoint()));
+		audience.add(asUrl(authorizationServerContext.getIssuer(), authorizationServerSettings.getTokenRevocationEndpoint()));
+		return audience;
+	}
+
+	private static String asUrl(String issuer, String endpoint) {
+		return UriComponentsBuilder.fromUriString(issuer).path(endpoint).build().toUriString();
+	}
+
+}

+ 7 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java

@@ -19,6 +19,7 @@ import org.springframework.http.HttpMethod;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider;
 import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
 import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
 import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
@@ -59,6 +60,12 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut
 						OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity),
 						OAuth2ConfigurerUtils.getTokenGenerator(httpSecurity));
 		httpSecurity.authenticationProvider(postProcess(oidcClientRegistrationAuthenticationProvider));
+
+		OidcClientConfigurationAuthenticationProvider oidcClientConfigurationAuthenticationProvider =
+				new OidcClientConfigurationAuthenticationProvider(
+						OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
+						OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity));
+		httpSecurity.authenticationProvider(postProcess(oidcClientConfigurationAuthenticationProvider));
 	}
 
 	@Override

+ 147 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java

@@ -0,0 +1,147 @@
+/*
+ * Copyright 2020-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.oauth2.server.authorization.oidc.authentication;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
+import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * An {@link AuthenticationProvider} implementation for OpenID Connect 1.0 Dynamic Client Configuration Endpoint.
+ *
+ * @author Ovidiu Popa
+ * @author Joe Grandja
+ * @author Rafal Lewczuk
+ * @since 0.4.0
+ * @see RegisteredClientRepository
+ * @see OAuth2AuthorizationService
+ * @see OidcClientRegistrationAuthenticationProvider
+ * @see <a href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientConfigurationEndpoint">4. Client Configuration Endpoint</a>
+ */
+public final class OidcClientConfigurationAuthenticationProvider implements AuthenticationProvider {
+	static final String DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE = "client.read";
+	private final RegisteredClientRepository registeredClientRepository;
+	private final OAuth2AuthorizationService authorizationService;
+	private final Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter;
+
+	/**
+	 * Constructs an {@code OidcClientConfigurationAuthenticationProvider} using the provided parameters.
+	 *
+	 * @param registeredClientRepository the repository of registered clients
+	 * @param authorizationService the authorization service
+	 */
+	public OidcClientConfigurationAuthenticationProvider(RegisteredClientRepository registeredClientRepository,
+			OAuth2AuthorizationService authorizationService) {
+		Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
+		Assert.notNull(authorizationService, "authorizationService cannot be null");
+		this.registeredClientRepository = registeredClientRepository;
+		this.authorizationService = authorizationService;
+		this.clientRegistrationConverter = new OidcClientRegistrationConverter();
+	}
+
+	@Override
+	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+		OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication =
+				(OidcClientRegistrationAuthenticationToken) authentication;
+
+		if (!StringUtils.hasText(clientRegistrationAuthentication.getClientId())) {
+			// This is not a Client Configuration Request.
+			// Return null to allow OidcClientRegistrationAuthenticationProvider to handle it.
+			return null;
+		}
+
+		// Validate the "registration" access token
+		AbstractOAuth2TokenAuthenticationToken<?> accessTokenAuthentication = null;
+		if (AbstractOAuth2TokenAuthenticationToken.class.isAssignableFrom(clientRegistrationAuthentication.getPrincipal().getClass())) {
+			accessTokenAuthentication = (AbstractOAuth2TokenAuthenticationToken<?>) clientRegistrationAuthentication.getPrincipal();
+		}
+		if (accessTokenAuthentication == null || !accessTokenAuthentication.isAuthenticated()) {
+			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
+		}
+
+		String accessTokenValue = accessTokenAuthentication.getToken().getTokenValue();
+		OAuth2Authorization authorization = this.authorizationService.findByToken(
+				accessTokenValue, OAuth2TokenType.ACCESS_TOKEN);
+		if (authorization == null) {
+			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
+		}
+
+		OAuth2Authorization.Token<OAuth2AccessToken> authorizedAccessToken = authorization.getAccessToken();
+		if (!authorizedAccessToken.isActive()) {
+			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
+		}
+		checkScope(authorizedAccessToken, Collections.singleton(DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE));
+
+		return findRegistration(clientRegistrationAuthentication, authorization);
+	}
+
+	@Override
+	public boolean supports(Class<?> authentication) {
+		return OidcClientRegistrationAuthenticationToken.class.isAssignableFrom(authentication);
+	}
+
+	private OidcClientRegistrationAuthenticationToken findRegistration(OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication,
+			OAuth2Authorization authorization) {
+
+		RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
+				clientRegistrationAuthentication.getClientId());
+		if (registeredClient == null) {
+			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
+		}
+
+		if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) {
+			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
+		}
+
+		OidcClientRegistration clientRegistration = this.clientRegistrationConverter.convert(registeredClient);
+
+		return new OidcClientRegistrationAuthenticationToken(
+				(Authentication) clientRegistrationAuthentication.getPrincipal(), clientRegistration);
+	}
+
+	@SuppressWarnings("unchecked")
+	private static void checkScope(OAuth2Authorization.Token<OAuth2AccessToken> authorizedAccessToken, Set<String> requiredScope) {
+		Collection<String> authorizedScope = Collections.emptySet();
+		if (authorizedAccessToken.getClaims().containsKey(OAuth2ParameterNames.SCOPE)) {
+			authorizedScope = (Collection<String>) authorizedAccessToken.getClaims().get(OAuth2ParameterNames.SCOPE);
+		}
+		if (!authorizedScope.containsAll(requiredScope)) {
+			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
+		} else if (authorizedScope.size() != requiredScope.size()) {
+			// Restrict the access token to only contain the required scope
+			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
+		}
+	}
+
+}

+ 99 - 172
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java

@@ -23,9 +23,11 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 
+import org.springframework.core.convert.converter.Converter;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
@@ -49,7 +51,6 @@ import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
 import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
-import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext;
 import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
 import org.springframework.security.oauth2.server.authorization.oidc.OidcClientMetadataClaimNames;
 import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
@@ -62,10 +63,9 @@ import org.springframework.security.oauth2.server.resource.authentication.Abstra
 import org.springframework.util.Assert;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
-import org.springframework.web.util.UriComponentsBuilder;
 
 /**
- * An {@link AuthenticationProvider} implementation for OpenID Connect 1.0 Dynamic Client Registration (and Configuration) Endpoint.
+ * An {@link AuthenticationProvider} implementation for OpenID Connect 1.0 Dynamic Client Registration Endpoint.
  *
  * @author Ovidiu Popa
  * @author Joe Grandja
@@ -74,20 +74,17 @@ import org.springframework.web.util.UriComponentsBuilder;
  * @see RegisteredClientRepository
  * @see OAuth2AuthorizationService
  * @see OAuth2TokenGenerator
+ * @see OidcClientConfigurationAuthenticationProvider
  * @see <a href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration">3. Client Registration Endpoint</a>
- * @see <a href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientConfigurationEndpoint">4. Client Configuration Endpoint</a>
  */
 public final class OidcClientRegistrationAuthenticationProvider implements AuthenticationProvider {
 	private static final String ERROR_URI = "https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError";
-	private static final StringKeyGenerator CLIENT_ID_GENERATOR = new Base64StringKeyGenerator(
-			Base64.getUrlEncoder().withoutPadding(), 32);
-	private static final StringKeyGenerator CLIENT_SECRET_GENERATOR = new Base64StringKeyGenerator(
-			Base64.getUrlEncoder().withoutPadding(), 48);
 	private static final String DEFAULT_CLIENT_REGISTRATION_AUTHORIZED_SCOPE = "client.create";
-	private static final String DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE = "client.read";
 	private final RegisteredClientRepository registeredClientRepository;
 	private final OAuth2AuthorizationService authorizationService;
 	private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
+	private final Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter;
+	private final Converter<OidcClientRegistration, RegisteredClient> registeredClientConverter;
 
 	/**
 	 * Constructs an {@code OidcClientRegistrationAuthenticationProvider} using the provided parameters.
@@ -105,6 +102,8 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 		this.registeredClientRepository = registeredClientRepository;
 		this.authorizationService = authorizationService;
 		this.tokenGenerator = tokenGenerator;
+		this.clientRegistrationConverter = new OidcClientRegistrationConverter();
+		this.registeredClientConverter = new RegisteredClientConverter();
 	}
 
 	@Override
@@ -112,7 +111,13 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 		OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication =
 				(OidcClientRegistrationAuthenticationToken) authentication;
 
-		// Validate the "initial" or "registration" access token
+		if (clientRegistrationAuthentication.getClientRegistration() == null) {
+			// This is not a Client Registration Request.
+			// Return null to allow OidcClientConfigurationAuthenticationProvider to handle it.
+			return null;
+		}
+
+		// Validate the "initial" access token
 		AbstractOAuth2TokenAuthenticationToken<?> accessTokenAuthentication = null;
 		if (AbstractOAuth2TokenAuthenticationToken.class.isAssignableFrom(clientRegistrationAuthentication.getPrincipal().getClass())) {
 			accessTokenAuthentication = (AbstractOAuth2TokenAuthenticationToken<?>) clientRegistrationAuthentication.getPrincipal();
@@ -122,7 +127,6 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 		}
 
 		String accessTokenValue = accessTokenAuthentication.getToken().getTokenValue();
-
 		OAuth2Authorization authorization = this.authorizationService.findByToken(
 				accessTokenValue, OAuth2TokenType.ACCESS_TOKEN);
 		if (authorization == null) {
@@ -133,10 +137,9 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 		if (!authorizedAccessToken.isActive()) {
 			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
 		}
+		checkScope(authorizedAccessToken, Collections.singleton(DEFAULT_CLIENT_REGISTRATION_AUTHORIZED_SCOPE));
 
-		return clientRegistrationAuthentication.getClientRegistration() != null ?
-				registerClient(clientRegistrationAuthentication, authorization) :
-				findRegistration(clientRegistrationAuthentication, authorization);
+		return registerClient(clientRegistrationAuthentication, authorization);
 	}
 
 	@Override
@@ -144,34 +147,9 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 		return OidcClientRegistrationAuthenticationToken.class.isAssignableFrom(authentication);
 	}
 
-	private OidcClientRegistrationAuthenticationToken findRegistration(OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication,
-			OAuth2Authorization authorization) {
-
-		OAuth2Authorization.Token<OAuth2AccessToken> authorizedAccessToken = authorization.getAccessToken();
-		checkScopeForConfiguration(authorizedAccessToken);
-
-		RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
-				clientRegistrationAuthentication.getClientId());
-		if (registeredClient == null) {
-			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
-		}
-
-		if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) {
-			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
-		}
-
-		OidcClientRegistration clientRegistration = buildRegistration(registeredClient).build();
-
-		return new OidcClientRegistrationAuthenticationToken(
-				(Authentication) clientRegistrationAuthentication.getPrincipal(), clientRegistration);
-	}
-
 	private OidcClientRegistrationAuthenticationToken registerClient(OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication,
 			OAuth2Authorization authorization) {
 
-		OAuth2Authorization.Token<OAuth2AccessToken> authorizedAccessToken = authorization.getAccessToken();
-		checkScopeForRegistration(authorizedAccessToken);
-
 		if (!isValidRedirectUris(clientRegistrationAuthentication.getClientRegistration().getRedirectUris())) {
 			throwInvalidClientRegistration(OAuth2ErrorCodes.INVALID_REDIRECT_URI, OidcClientMetadataClaimNames.REDIRECT_URIS);
 		}
@@ -180,19 +158,20 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 			throwInvalidClientRegistration("invalid_client_metadata", OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD);
 		}
 
-		RegisteredClient registeredClient = createClient(clientRegistrationAuthentication.getClientRegistration());
+		RegisteredClient registeredClient = this.registeredClientConverter.convert(clientRegistrationAuthentication.getClientRegistration());
 		this.registeredClientRepository.save(registeredClient);
 
 		OAuth2Authorization registeredClientAuthorization = registerAccessToken(registeredClient);
 
 		// Invalidate the "initial" access token as it can only be used once
-		authorization = OidcAuthenticationProviderUtils.invalidate(authorization, authorizedAccessToken.getToken());
+		authorization = OidcAuthenticationProviderUtils.invalidate(authorization, authorization.getAccessToken().getToken());
 		if (authorization.getRefreshToken() != null) {
 			authorization = OidcAuthenticationProviderUtils.invalidate(authorization, authorization.getRefreshToken().getToken());
 		}
 		this.authorizationService.save(authorization);
 
-		OidcClientRegistration clientRegistration = buildRegistration(registeredClient)
+		Map<String, Object> clientRegistrationClaims = this.clientRegistrationConverter.convert(registeredClient).getClaims();
+		OidcClientRegistration clientRegistration = OidcClientRegistration.withClaims(clientRegistrationClaims)
 				.registrationAccessToken(registeredClientAuthorization.getAccessToken().getToken().getTokenValue())
 				.build();
 
@@ -205,7 +184,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 				registeredClient.getClientAuthenticationMethods().iterator().next(), registeredClient.getClientSecret());
 
 		Set<String> authorizedScopes = new HashSet<>();
-		authorizedScopes.add(DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE);
+		authorizedScopes.add(OidcClientConfigurationAuthenticationProvider.DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE);
 		authorizedScopes = Collections.unmodifiableSet(authorizedScopes);
 
 		// @formatter:off
@@ -249,66 +228,6 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 		return authorization;
 	}
 
-	private OidcClientRegistration.Builder buildRegistration(RegisteredClient registeredClient) {
-		// @formatter:off
-		OidcClientRegistration.Builder builder = OidcClientRegistration.builder()
-				.clientId(registeredClient.getClientId())
-				.clientIdIssuedAt(registeredClient.getClientIdIssuedAt())
-				.clientName(registeredClient.getClientName());
-
-		if (registeredClient.getClientSecret() != null) {
-			builder.clientSecret(registeredClient.getClientSecret());
-		}
-
-		builder.redirectUris(redirectUris ->
-				redirectUris.addAll(registeredClient.getRedirectUris()));
-
-		builder.grantTypes(grantTypes ->
-				registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
-						grantTypes.add(authorizationGrantType.getValue())));
-
-		if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
-			builder.responseType(OAuth2AuthorizationResponseType.CODE.getValue());
-		}
-
-		if (!CollectionUtils.isEmpty(registeredClient.getScopes())) {
-			builder.scopes(scopes ->
-					scopes.addAll(registeredClient.getScopes()));
-		}
-
-		AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
-		String registrationClientUri = UriComponentsBuilder.fromUriString(authorizationServerContext.getIssuer())
-				.path(authorizationServerContext.getAuthorizationServerSettings().getOidcClientRegistrationEndpoint())
-				.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
-				.toUriString();
-
-		builder
-				.tokenEndpointAuthenticationMethod(registeredClient.getClientAuthenticationMethods().iterator().next().getValue())
-				.idTokenSignedResponseAlgorithm(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName())
-				.registrationClientUrl(registrationClientUri);
-
-		ClientSettings clientSettings = registeredClient.getClientSettings();
-
-		if (clientSettings.getJwkSetUrl() != null) {
-			builder.jwkSetUrl(clientSettings.getJwkSetUrl());
-		}
-
-		if (clientSettings.getTokenEndpointAuthenticationSigningAlgorithm() != null) {
-			builder.tokenEndpointAuthenticationSigningAlgorithm(clientSettings.getTokenEndpointAuthenticationSigningAlgorithm().getName());
-		}
-
-		return builder;
-		// @formatter:on
-	}
-
-	private static void checkScopeForRegistration(OAuth2Authorization.Token<OAuth2AccessToken> authorizedAccessToken) {
-		checkScope(authorizedAccessToken, Collections.singleton(DEFAULT_CLIENT_REGISTRATION_AUTHORIZED_SCOPE));
-	}
-
-	private static void checkScopeForConfiguration(OAuth2Authorization.Token<OAuth2AccessToken> authorizedAccessToken) {
-		checkScope(authorizedAccessToken, Collections.singleton(DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE));
-	}
-
 	@SuppressWarnings("unchecked")
 	private static void checkScope(OAuth2Authorization.Token<OAuth2AccessToken> authorizedAccessToken, Set<String> requiredScope) {
 		Collection<String> authorizedScope = Collections.emptySet();
@@ -366,84 +285,92 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 		}
 	}
 
-	private static RegisteredClient createClient(OidcClientRegistration clientRegistration) {
-		// @formatter:off
-		RegisteredClient.Builder builder = RegisteredClient.withId(UUID.randomUUID().toString())
-				.clientId(CLIENT_ID_GENERATOR.generateKey())
-				.clientIdIssuedAt(Instant.now())
-				.clientName(clientRegistration.getClientName());
+	private static void throwInvalidClientRegistration(String errorCode, String fieldName) {
+		OAuth2Error error = new OAuth2Error(
+				errorCode,
+				"Invalid Client Registration: " + fieldName,
+				ERROR_URI);
+		throw new OAuth2AuthenticationException(error);
+	}
 
-		if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
-			builder
-					.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
-					.clientSecret(CLIENT_SECRET_GENERATOR.generateKey());
-		} else if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
-			builder
-					.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
-					.clientSecret(CLIENT_SECRET_GENERATOR.generateKey());
-		} else if (ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
-			builder.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
-		} else {
-			builder
-					.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
-					.clientSecret(CLIENT_SECRET_GENERATOR.generateKey());
-		}
+	private static final class RegisteredClientConverter implements Converter<OidcClientRegistration, RegisteredClient> {
+		private static final StringKeyGenerator CLIENT_ID_GENERATOR = new Base64StringKeyGenerator(
+				Base64.getUrlEncoder().withoutPadding(), 32);
+		private static final StringKeyGenerator CLIENT_SECRET_GENERATOR = new Base64StringKeyGenerator(
+				Base64.getUrlEncoder().withoutPadding(), 48);
+
+		@Override
+		public RegisteredClient convert(OidcClientRegistration clientRegistration) {
+			// @formatter:off
+			RegisteredClient.Builder builder = RegisteredClient.withId(UUID.randomUUID().toString())
+					.clientId(CLIENT_ID_GENERATOR.generateKey())
+					.clientIdIssuedAt(Instant.now())
+					.clientName(clientRegistration.getClientName());
+
+			if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
+				builder
+						.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
+						.clientSecret(CLIENT_SECRET_GENERATOR.generateKey());
+			} else if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
+				builder
+						.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
+						.clientSecret(CLIENT_SECRET_GENERATOR.generateKey());
+			} else if (ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
+				builder.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
+			} else {
+				builder
+						.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
+						.clientSecret(CLIENT_SECRET_GENERATOR.generateKey());
+			}
 
-		builder.redirectUris(redirectUris ->
-				redirectUris.addAll(clientRegistration.getRedirectUris()));
+			builder.redirectUris(redirectUris ->
+					redirectUris.addAll(clientRegistration.getRedirectUris()));
 
-		if (!CollectionUtils.isEmpty(clientRegistration.getGrantTypes())) {
-			builder.authorizationGrantTypes(authorizationGrantTypes ->
-					clientRegistration.getGrantTypes().forEach(grantType ->
-							authorizationGrantTypes.add(new AuthorizationGrantType(grantType))));
-		} else {
-			builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
-		}
-		if (CollectionUtils.isEmpty(clientRegistration.getResponseTypes()) ||
-				clientRegistration.getResponseTypes().contains(OAuth2AuthorizationResponseType.CODE.getValue())) {
-			builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
-		}
+			if (!CollectionUtils.isEmpty(clientRegistration.getGrantTypes())) {
+				builder.authorizationGrantTypes(authorizationGrantTypes ->
+						clientRegistration.getGrantTypes().forEach(grantType ->
+								authorizationGrantTypes.add(new AuthorizationGrantType(grantType))));
+			} else {
+				builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
+			}
+			if (CollectionUtils.isEmpty(clientRegistration.getResponseTypes()) ||
+					clientRegistration.getResponseTypes().contains(OAuth2AuthorizationResponseType.CODE.getValue())) {
+				builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
+			}
 
-		if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) {
-			builder.scopes(scopes ->
-					scopes.addAll(clientRegistration.getScopes()));
-		}
+			if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) {
+				builder.scopes(scopes ->
+						scopes.addAll(clientRegistration.getScopes()));
+			}
 
-		ClientSettings.Builder clientSettingsBuilder = ClientSettings.builder()
-				.requireProofKey(true)
-				.requireAuthorizationConsent(true);
+			ClientSettings.Builder clientSettingsBuilder = ClientSettings.builder()
+					.requireProofKey(true)
+					.requireAuthorizationConsent(true);
 
-		if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
-			MacAlgorithm macAlgorithm = MacAlgorithm.from(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm());
-			if (macAlgorithm == null) {
-				macAlgorithm = MacAlgorithm.HS256;
-			}
-			clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(macAlgorithm);
-		} else if (ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
-			SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.from(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm());
-			if (signatureAlgorithm == null) {
-				signatureAlgorithm = SignatureAlgorithm.RS256;
+			if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
+				MacAlgorithm macAlgorithm = MacAlgorithm.from(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm());
+				if (macAlgorithm == null) {
+					macAlgorithm = MacAlgorithm.HS256;
+				}
+				clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(macAlgorithm);
+			} else if (ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
+				SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.from(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm());
+				if (signatureAlgorithm == null) {
+					signatureAlgorithm = SignatureAlgorithm.RS256;
+				}
+				clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(signatureAlgorithm);
+				clientSettingsBuilder.jwkSetUrl(clientRegistration.getJwkSetUrl().toString());
 			}
-			clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(signatureAlgorithm);
-			clientSettingsBuilder.jwkSetUrl(clientRegistration.getJwkSetUrl().toString());
-		}
 
-		builder
-				.clientSettings(clientSettingsBuilder.build())
-				.tokenSettings(TokenSettings.builder()
-						.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
-						.build());
+			builder
+					.clientSettings(clientSettingsBuilder.build())
+					.tokenSettings(TokenSettings.builder()
+							.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
+							.build());
 
-		return builder.build();
-		// @formatter:on
-	}
+			return builder.build();
+			// @formatter:on
+		}
 
-	private static void throwInvalidClientRegistration(String errorCode, String fieldName) {
-		OAuth2Error error = new OAuth2Error(
-				errorCode,
-				"Invalid Client Registration: " + fieldName,
-				ERROR_URI);
-		throw new OAuth2AuthenticationException(error);
 	}
-
 }

+ 1 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java

@@ -33,6 +33,7 @@ import org.springframework.util.Assert;
  * @see AbstractAuthenticationToken
  * @see OidcClientRegistration
  * @see OidcClientRegistrationAuthenticationProvider
+ * @see OidcClientConfigurationAuthenticationProvider
  */
 public class OidcClientRegistrationAuthenticationToken extends AbstractAuthenticationToken {
 	private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;

+ 89 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationConverter.java

@@ -0,0 +1,89 @@
+/*
+ * Copyright 2020-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.oauth2.server.authorization.oidc.authentication;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext;
+import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
+import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
+import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.util.UriComponentsBuilder;
+
+/**
+ * @author Joe Grandja
+ * @since 0.4.0
+ */
+final class OidcClientRegistrationConverter implements Converter<RegisteredClient, OidcClientRegistration> {
+
+	@Override
+	public OidcClientRegistration convert(RegisteredClient registeredClient) {
+		// @formatter:off
+		OidcClientRegistration.Builder builder = OidcClientRegistration.builder()
+				.clientId(registeredClient.getClientId())
+				.clientIdIssuedAt(registeredClient.getClientIdIssuedAt())
+				.clientName(registeredClient.getClientName());
+
+		if (registeredClient.getClientSecret() != null) {
+			builder.clientSecret(registeredClient.getClientSecret());
+		}
+
+		builder.redirectUris(redirectUris ->
+				redirectUris.addAll(registeredClient.getRedirectUris()));
+
+		builder.grantTypes(grantTypes ->
+				registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
+						grantTypes.add(authorizationGrantType.getValue())));
+
+		if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
+			builder.responseType(OAuth2AuthorizationResponseType.CODE.getValue());
+		}
+
+		if (!CollectionUtils.isEmpty(registeredClient.getScopes())) {
+			builder.scopes(scopes ->
+					scopes.addAll(registeredClient.getScopes()));
+		}
+
+		AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
+		String registrationClientUri = UriComponentsBuilder.fromUriString(authorizationServerContext.getIssuer())
+				.path(authorizationServerContext.getAuthorizationServerSettings().getOidcClientRegistrationEndpoint())
+				.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
+				.toUriString();
+
+		builder
+				.tokenEndpointAuthenticationMethod(registeredClient.getClientAuthenticationMethods().iterator().next().getValue())
+				.idTokenSignedResponseAlgorithm(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName())
+				.registrationClientUrl(registrationClientUri);
+
+		ClientSettings clientSettings = registeredClient.getClientSettings();
+
+		if (clientSettings.getJwkSetUrl() != null) {
+			builder.jwkSetUrl(clientSettings.getJwkSetUrl());
+		}
+
+		if (clientSettings.getTokenEndpointAuthenticationSigningAlgorithm() != null) {
+			builder.tokenEndpointAuthenticationSigningAlgorithm(clientSettings.getTokenEndpointAuthenticationSigningAlgorithm().getName());
+		}
+
+		return builder.build();
+		// @formatter:on
+	}
+
+}

+ 16 - 27
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java

@@ -25,10 +25,8 @@ import jakarta.servlet.http.HttpServletResponse;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.converter.HttpMessageConverter;
-import org.springframework.http.server.ServletServerHttpRequest;
 import org.springframework.http.server.ServletServerHttpResponse;
 import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
@@ -36,8 +34,12 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
 import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
+import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider;
+import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
 import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter;
+import org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcClientRegistrationAuthenticationConverter;
+import org.springframework.security.web.authentication.AuthenticationConverter;
 import org.springframework.security.web.util.matcher.AndRequestMatcher;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.OrRequestMatcher;
@@ -47,12 +49,15 @@ import org.springframework.util.StringUtils;
 import org.springframework.web.filter.OncePerRequestFilter;
 
 /**
- * A {@code Filter} that processes OpenID Connect Dynamic Client Registration (and Configuration) 1.0 Requests.
+ * A {@code Filter} that processes OpenID Connect 1.0 Dynamic Client Registration (and Client Read) Requests.
  *
  * @author Ovidiu Popa
  * @author Joe Grandja
  * @since 0.1.1
  * @see OidcClientRegistration
+ * @see OidcClientRegistrationAuthenticationConverter
+ * @see OidcClientRegistrationAuthenticationProvider
+ * @see OidcClientConfigurationAuthenticationProvider
  * @see <a href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration">3. Client Registration Endpoint</a>
  * @see <a href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientConfigurationEndpoint">4. Client Configuration Endpoint</a>
  */
@@ -68,6 +73,7 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
 			new OidcClientRegistrationHttpMessageConverter();
 	private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
 			new OAuth2ErrorHttpMessageConverter();
+	private AuthenticationConverter authenticationConverter;
 
 	/**
 	 * Constructs an {@code OidcClientRegistrationEndpointFilter} using the provided parameters.
@@ -92,11 +98,12 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
 		this.clientRegistrationEndpointMatcher = new OrRequestMatcher(
 				new AntPathRequestMatcher(
 						clientRegistrationEndpointUri, HttpMethod.POST.name()),
-				createConfigureClientMatcher(clientRegistrationEndpointUri));
+				createClientConfigurationMatcher(clientRegistrationEndpointUri));
+		this.authenticationConverter = new OidcClientRegistrationAuthenticationConverter();
 	}
 
-	private static RequestMatcher createConfigureClientMatcher(String clientRegistrationEndpointUri) {
-		RequestMatcher configureClientGetMatcher = new AntPathRequestMatcher(
+	private static RequestMatcher createClientConfigurationMatcher(String clientRegistrationEndpointUri) {
+		RequestMatcher clientConfigurationGetMatcher = new AntPathRequestMatcher(
 				clientRegistrationEndpointUri, HttpMethod.GET.name());
 
 		RequestMatcher clientIdMatcher = request -> {
@@ -104,7 +111,7 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
 			return StringUtils.hasText(clientId);
 		};
 
-		return new AndRequestMatcher(configureClientGetMatcher, clientIdMatcher);
+		return new AndRequestMatcher(clientConfigurationGetMatcher, clientIdMatcher);
 	}
 
 	@Override
@@ -117,7 +124,8 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
 		}
 
 		try {
-			OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication = convert(request);
+			OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication =
+					(OidcClientRegistrationAuthenticationToken) this.authenticationConverter.convert(request);
 
 			OidcClientRegistrationAuthenticationToken clientRegistrationAuthenticationResult =
 					(OidcClientRegistrationAuthenticationToken) this.authenticationManager.authenticate(clientRegistrationAuthentication);
@@ -142,25 +150,6 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
 		}
 	}
 
-	private OidcClientRegistrationAuthenticationToken convert(HttpServletRequest request) throws Exception {
-		Authentication principal = SecurityContextHolder.getContext().getAuthentication();
-
-		if ("POST".equals(request.getMethod())) {
-			OidcClientRegistration clientRegistration = this.clientRegistrationHttpMessageConverter.read(
-					OidcClientRegistration.class, new ServletServerHttpRequest(request));
-			return new OidcClientRegistrationAuthenticationToken(principal, clientRegistration);
-		}
-
-		// client_id (REQUIRED)
-		String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID);
-		String[] clientIdParameters = request.getParameterValues(OAuth2ParameterNames.CLIENT_ID);
-		if (!StringUtils.hasText(clientId) || clientIdParameters.length != 1) {
-			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
-		}
-
-		return new OidcClientRegistrationAuthenticationToken(principal, clientId);
-	}
-
 	private void sendClientRegistrationResponse(HttpServletResponse response, HttpStatus httpStatus, OidcClientRegistration clientRegistration) throws IOException {
 		ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
 		httpResponse.setStatusCode(httpStatus);

+ 78 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcClientRegistrationAuthenticationConverter.java

@@ -0,0 +1,78 @@
+/*
+ * Copyright 2020-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.oauth2.server.authorization.oidc.web.authentication;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
+import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter;
+import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
+import org.springframework.security.web.authentication.AuthenticationConverter;
+import org.springframework.util.StringUtils;
+
+/**
+ * Attempts to extract an OpenID Connect 1.0 Dynamic Client Registration (or Client Read) Request from {@link HttpServletRequest}
+ * and then converts to an {@link OidcClientRegistrationAuthenticationToken} used for authenticating the request.
+ *
+ * @author Joe Grandja
+ * @since 0.4.0
+ * @see AuthenticationConverter
+ * @see OidcClientRegistrationAuthenticationToken
+ * @see OidcClientRegistrationEndpointFilter
+ */
+public final class OidcClientRegistrationAuthenticationConverter implements AuthenticationConverter {
+	private final HttpMessageConverter<OidcClientRegistration> clientRegistrationHttpMessageConverter =
+			new OidcClientRegistrationHttpMessageConverter();
+
+	@Override
+	public Authentication convert(HttpServletRequest request) {
+		Authentication principal = SecurityContextHolder.getContext().getAuthentication();
+
+		if ("POST".equals(request.getMethod())) {
+			OidcClientRegistration clientRegistration;
+			try {
+				clientRegistration = this.clientRegistrationHttpMessageConverter.read(
+						OidcClientRegistration.class, new ServletServerHttpRequest(request));
+			} catch (Exception ex) {
+				OAuth2Error error = new OAuth2Error(
+						OAuth2ErrorCodes.INVALID_REQUEST,
+						"OpenID Client Registration Error: " + ex.getMessage(),
+						"https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError");
+				throw new OAuth2AuthenticationException(error, ex);
+			}
+			return new OidcClientRegistrationAuthenticationToken(principal, clientRegistration);
+		}
+
+		// client_id (REQUIRED)
+		String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID);
+		if (!StringUtils.hasText(clientId) ||
+				request.getParameterValues(OAuth2ParameterNames.CLIENT_ID).length != 1) {
+			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
+		}
+
+		return new OidcClientRegistrationAuthenticationToken(principal, clientId);
+	}
+
+}

+ 7 - 79
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProviderTests.java

@@ -41,7 +41,6 @@ import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
 import org.springframework.security.oauth2.jose.TestJwks;
 import org.springframework.security.oauth2.jose.TestKeys;
 import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
-import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
 import org.springframework.security.oauth2.jwt.BadJwtException;
 import org.springframework.security.oauth2.jwt.JwsHeader;
 import org.springframework.security.oauth2.jwt.Jwt;
@@ -117,6 +116,13 @@ public class JwtClientAssertionAuthenticationProviderTests {
 				.hasMessage("authorizationService cannot be null");
 	}
 
+	@Test
+	public void setJwtDecoderFactoryWhenNullThenThrowIllegalArgumentException() {
+		assertThatThrownBy(() -> this.authenticationProvider.setJwtDecoderFactory(null))
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("jwtDecoderFactory cannot be null");
+	}
+
 	@Test
 	public void supportsWhenTypeOAuth2ClientAuthenticationTokenThenReturnTrue() {
 		assertThat(this.authenticationProvider.supports(OAuth2ClientAuthenticationToken.class)).isTrue();
@@ -181,84 +187,6 @@ public class JwtClientAssertionAuthenticationProviderTests {
 				});
 	}
 
-	@Test
-	public void authenticateWhenMissingJwkSetUrlThenThrowOAuth2AuthenticationException() {
-		// @formatter:off
-		RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
-				.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
-				.clientSettings(
-						ClientSettings.builder()
-								.tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS256)
-								.build()
-				)
-				.build();
-		// @formatter:on
-		when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
-				.thenReturn(registeredClient);
-
-		OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
-				registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, "jwt-assertion", null);
-		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
-				.isInstanceOf(OAuth2AuthenticationException.class)
-				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
-				.satisfies(error -> {
-					assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
-					assertThat(error.getDescription()).isEqualTo("Failed to find a Signature Verifier for Client: '" +
-							registeredClient.getId() + "'. Check to ensure you have configured the JWK Set URL.");
-				});
-	}
-
-	@Test
-	public void authenticateWhenMissingClientSecretThenThrowOAuth2AuthenticationException() {
-		// @formatter:off
-		RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
-				.clientSecret(null)
-				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
-				.clientSettings(
-						ClientSettings.builder()
-								.tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256)
-								.build()
-				)
-				.build();
-		// @formatter:on
-		when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
-				.thenReturn(registeredClient);
-
-		OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
-				registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, "jwt-assertion", null);
-		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
-				.isInstanceOf(OAuth2AuthenticationException.class)
-				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
-				.satisfies(error -> {
-					assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
-					assertThat(error.getDescription()).isEqualTo("Failed to find a Signature Verifier for Client: '" +
-							registeredClient.getId() + "'. Check to ensure you have configured the client secret.");
-				});
-	}
-
-	@Test
-	public void authenticateWhenMissingSigningAlgorithmThenThrowOAuth2AuthenticationException() {
-		// @formatter:off
-		RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
-				.clientSecret(TestKeys.DEFAULT_ENCODED_SECRET_KEY)
-				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
-				.build();
-		// @formatter:on
-		when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
-				.thenReturn(registeredClient);
-
-		OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
-				registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, "jwt-assertion", null);
-		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
-				.isInstanceOf(OAuth2AuthenticationException.class)
-				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
-				.satisfies(error -> {
-					assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
-					assertThat(error.getDescription()).isEqualTo("Failed to find a Signature Verifier for Client: '" +
-							registeredClient.getId() + "'. Check to ensure you have configured a valid JWS Algorithm: 'null'.");
-				});
-	}
-
 	@Test
 	public void authenticateWhenInvalidCredentialsThenThrowOAuth2AuthenticationException() {
 		// @formatter:off

+ 112 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionDecoderFactoryTests.java

@@ -0,0 +1,112 @@
+/*
+ * Copyright 2020-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.oauth2.server.authorization.authentication;
+
+import org.junit.Test;
+
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
+import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
+import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for {@link JwtClientAssertionDecoderFactory}.
+ *
+ * @author Joe Grandja
+ */
+public class JwtClientAssertionDecoderFactoryTests {
+	private JwtClientAssertionDecoderFactory jwtDecoderFactory = new JwtClientAssertionDecoderFactory();
+
+	@Test
+	public void setJwtValidatorFactoryWhenNullThenThrowIllegalArgumentException() {
+		assertThatThrownBy(() -> this.jwtDecoderFactory.setJwtValidatorFactory(null))
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("jwtValidatorFactory cannot be null");
+	}
+
+	@Test
+	public void createDecoderWhenMissingJwkSetUrlThenThrowOAuth2AuthenticationException() {
+		// @formatter:off
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
+				.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
+				.clientSettings(
+						ClientSettings.builder()
+								.tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS256)
+								.build()
+				)
+				.build();
+		// @formatter:on
+
+		assertThatThrownBy(() -> this.jwtDecoderFactory.createDecoder(registeredClient))
+				.isInstanceOf(OAuth2AuthenticationException.class)
+				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
+				.satisfies(error -> {
+					assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
+					assertThat(error.getDescription()).isEqualTo("Failed to find a Signature Verifier for Client: '" +
+							registeredClient.getId() + "'. Check to ensure you have configured the JWK Set URL.");
+				});
+	}
+
+	@Test
+	public void createDecoderWhenMissingClientSecretThenThrowOAuth2AuthenticationException() {
+		// @formatter:off
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
+				.clientSecret(null)
+				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
+				.clientSettings(
+						ClientSettings.builder()
+								.tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256)
+								.build()
+				)
+				.build();
+		// @formatter:on
+
+		assertThatThrownBy(() -> this.jwtDecoderFactory.createDecoder(registeredClient))
+				.isInstanceOf(OAuth2AuthenticationException.class)
+				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
+				.satisfies(error -> {
+					assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
+					assertThat(error.getDescription()).isEqualTo("Failed to find a Signature Verifier for Client: '" +
+							registeredClient.getId() + "'. Check to ensure you have configured the client secret.");
+				});
+	}
+
+	@Test
+	public void createDecoderWhenMissingSigningAlgorithmThenThrowOAuth2AuthenticationException() {
+		// @formatter:off
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
+				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
+				.build();
+		// @formatter:on
+
+		assertThatThrownBy(() -> this.jwtDecoderFactory.createDecoder(registeredClient))
+				.isInstanceOf(OAuth2AuthenticationException.class)
+				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
+				.satisfies(error -> {
+					assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
+					assertThat(error.getDescription()).isEqualTo("Failed to find a Signature Verifier for Client: '" +
+							registeredClient.getId() + "'. Check to ensure you have configured a valid JWS Algorithm: 'null'.");
+				});
+	}
+
+}

+ 400 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProviderTests.java

@@ -0,0 +1,400 @@
+/*
+ * Copyright 2020-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.oauth2.server.authorization.oidc.authentication;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
+import org.springframework.security.oauth2.jwt.JwsHeader;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtClaimsSet;
+import org.springframework.security.oauth2.jwt.TestJwsHeaders;
+import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
+import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
+import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext;
+import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
+import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext;
+import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
+import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
+import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link OidcClientConfigurationAuthenticationProvider}.
+ *
+ * @author Ovidiu Popa
+ * @author Joe Grandja
+ */
+public class OidcClientConfigurationAuthenticationProviderTests {
+	private RegisteredClientRepository registeredClientRepository;
+	private OAuth2AuthorizationService authorizationService;
+	private AuthorizationServerSettings authorizationServerSettings;
+	private OidcClientConfigurationAuthenticationProvider authenticationProvider;
+
+	@Before
+	public void setUp() {
+		this.registeredClientRepository = mock(RegisteredClientRepository.class);
+		this.authorizationService = mock(OAuth2AuthorizationService.class);
+		this.authorizationServerSettings = AuthorizationServerSettings.builder().issuer("https://provider.com").build();
+		AuthorizationServerContextHolder.setContext(new TestAuthorizationServerContext(this.authorizationServerSettings, null));
+		this.authenticationProvider = new OidcClientConfigurationAuthenticationProvider(
+				this.registeredClientRepository, this.authorizationService);
+	}
+
+	@After
+	public void cleanup() {
+		AuthorizationServerContextHolder.resetContext();
+	}
+
+	@Test
+	public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> new OidcClientConfigurationAuthenticationProvider(null, this.authorizationService))
+				.withMessage("registeredClientRepository cannot be null");
+	}
+
+	@Test
+	public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() {
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> new OidcClientConfigurationAuthenticationProvider(this.registeredClientRepository, null))
+				.withMessage("authorizationService cannot be null");
+	}
+
+	@Test
+	public void supportsWhenTypeOidcClientRegistrationAuthenticationTokenThenReturnTrue() {
+		assertThat(this.authenticationProvider.supports(OidcClientRegistrationAuthenticationToken.class)).isTrue();
+	}
+
+	@Test
+	public void authenticateWhenPrincipalNotOAuth2TokenAuthenticationTokenThenThrowOAuth2AuthenticationException() {
+		TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
+
+		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+				principal, "client-id");
+
+		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+				.isInstanceOf(OAuth2AuthenticationException.class)
+				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+				.isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
+	}
+
+	@Test
+	public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
+		JwtAuthenticationToken principal = new JwtAuthenticationToken(createJwtClientConfiguration());
+
+		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+				principal, "client-id");
+
+		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+				.isInstanceOf(OAuth2AuthenticationException.class)
+				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+				.isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
+	}
+
+	@Test
+	public void authenticateWhenAccessTokenNotFoundThenThrowOAuth2AuthenticationException() {
+		Jwt jwt = createJwtClientConfiguration();
+		JwtAuthenticationToken principal = new JwtAuthenticationToken(
+				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
+
+		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+				principal, "client-id");
+
+		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+				.isInstanceOf(OAuth2AuthenticationException.class)
+				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+				.isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
+		verify(this.authorizationService).findByToken(
+				eq(jwt.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
+	}
+
+	@Test
+	public void authenticateWhenAccessTokenNotActiveThenThrowOAuth2AuthenticationException() {
+		Jwt jwt = createJwtClientConfiguration();
+		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+				jwt.getTokenValue(), jwt.getIssuedAt(),
+				jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
+				registeredClient, jwtAccessToken, jwt.getClaims()).build();
+		authorization = OidcAuthenticationProviderUtils.invalidate(authorization, jwtAccessToken);
+		when(this.authorizationService.findByToken(
+				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
+				.thenReturn(authorization);
+
+		JwtAuthenticationToken principal = new JwtAuthenticationToken(
+				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
+
+		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+				principal, registeredClient.getClientId());
+
+		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+				.isInstanceOf(OAuth2AuthenticationException.class)
+				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+				.isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
+		verify(this.authorizationService).findByToken(
+				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
+	}
+
+	@Test
+	public void authenticateWhenAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() {
+		Jwt jwt = createJwt(Collections.singleton("unauthorized.scope"));
+		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+				jwt.getTokenValue(), jwt.getIssuedAt(),
+				jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
+				registeredClient, jwtAccessToken, jwt.getClaims()).build();
+		when(this.authorizationService.findByToken(
+				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
+				.thenReturn(authorization);
+
+		JwtAuthenticationToken principal = new JwtAuthenticationToken(
+				jwt, AuthorityUtils.createAuthorityList("SCOPE_unauthorized.scope"));
+
+		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+				principal, registeredClient.getClientId());
+
+		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+				.isInstanceOf(OAuth2AuthenticationException.class)
+				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+				.isEqualTo(OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
+		verify(this.authorizationService).findByToken(
+				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
+	}
+
+	@Test
+	public void authenticateWhenAccessTokenContainsRequiredScopeAndAdditionalScopeThenThrowOAuth2AuthenticationException() {
+		Jwt jwt = createJwt(new HashSet<>(Arrays.asList("client.read", "scope1")));
+		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+				jwt.getTokenValue(), jwt.getIssuedAt(),
+				jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
+				registeredClient, jwtAccessToken, jwt.getClaims()).build();
+		when(this.authorizationService.findByToken(
+				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
+				.thenReturn(authorization);
+
+		JwtAuthenticationToken principal = new JwtAuthenticationToken(
+				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read", "SCOPE_scope1"));
+
+		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+				principal, registeredClient.getClientId());
+
+		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+				.isInstanceOf(OAuth2AuthenticationException.class)
+				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+				.isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
+		verify(this.authorizationService).findByToken(
+				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
+	}
+
+	@Test
+	public void authenticateWhenRegisteredClientNotFoundThenThrowOAuth2AuthenticationException() {
+		Jwt jwt = createJwtClientConfiguration();
+		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+				jwt.getTokenValue(), jwt.getIssuedAt(),
+				jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
+				registeredClient, jwtAccessToken, jwt.getClaims()).build();
+		when(this.authorizationService.findByToken(
+				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
+				.thenReturn(authorization);
+
+		JwtAuthenticationToken principal = new JwtAuthenticationToken(
+				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
+
+		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+				principal, registeredClient.getClientId());
+
+		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+				.isInstanceOf(OAuth2AuthenticationException.class)
+				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+				.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
+		verify(this.authorizationService).findByToken(
+				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
+		verify(this.registeredClientRepository).findByClientId(
+				eq(registeredClient.getClientId()));
+	}
+
+	@Test
+	public void authenticateWhenClientIdNotEqualToAuthorizedClientThenThrowOAuth2AuthenticationException() {
+		Jwt jwt = createJwtClientConfiguration();
+		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+				jwt.getTokenValue(), jwt.getIssuedAt(),
+				jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient()
+				.id("registration-2").clientId("client-2").build();
+		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
+				authorizedRegisteredClient, jwtAccessToken, jwt.getClaims()).build();
+		when(this.authorizationService.findByToken(
+				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
+				.thenReturn(authorization);
+		when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
+				.thenReturn(registeredClient);
+
+		JwtAuthenticationToken principal = new JwtAuthenticationToken(
+				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
+
+		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+				principal, registeredClient.getClientId());
+
+		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+				.isInstanceOf(OAuth2AuthenticationException.class)
+				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+				.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
+		verify(this.authorizationService).findByToken(
+				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
+		verify(this.registeredClientRepository).findByClientId(
+				eq(registeredClient.getClientId()));
+	}
+
+	@Test
+	public void authenticateWhenValidAccessTokenThenReturnClientRegistration() {
+		Jwt jwt = createJwtClientConfiguration();
+		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+				jwt.getTokenValue(), jwt.getIssuedAt(),
+				jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
+				.clientAuthenticationMethods((clientAuthenticationMethods) -> {
+					clientAuthenticationMethods.clear();
+					clientAuthenticationMethods.add(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
+				})
+				.clientSettings(
+						ClientSettings.builder()
+								.tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS512)
+								.jwkSetUrl("https://client.example.com/jwks")
+								.build()
+				)
+				.build();
+		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
+				registeredClient, jwtAccessToken, jwt.getClaims()).build();
+		when(this.authorizationService.findByToken(
+				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
+				.thenReturn(authorization);
+		when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
+				.thenReturn(registeredClient);
+
+		JwtAuthenticationToken principal = new JwtAuthenticationToken(
+				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
+
+		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+				principal, registeredClient.getClientId());
+
+		OidcClientRegistrationAuthenticationToken authenticationResult =
+				(OidcClientRegistrationAuthenticationToken) this.authenticationProvider.authenticate(authentication);
+
+		verify(this.authorizationService).findByToken(
+				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
+		verify(this.registeredClientRepository).findByClientId(
+				eq(registeredClient.getClientId()));
+
+		// verify that the "registration" access token is not invalidated after it is used
+		verify(this.authorizationService, never()).save(eq(authorization));
+		assertThat(authorization.getAccessToken().isInvalidated()).isFalse();
+
+		OidcClientRegistration clientRegistrationResult = authenticationResult.getClientRegistration();
+		assertThat(clientRegistrationResult.getClientId()).isEqualTo(registeredClient.getClientId());
+		assertThat(clientRegistrationResult.getClientIdIssuedAt()).isEqualTo(registeredClient.getClientIdIssuedAt());
+		assertThat(clientRegistrationResult.getClientSecret()).isEqualTo(registeredClient.getClientSecret());
+		assertThat(clientRegistrationResult.getClientSecretExpiresAt()).isEqualTo(registeredClient.getClientSecretExpiresAt());
+		assertThat(clientRegistrationResult.getClientName()).isEqualTo(registeredClient.getClientName());
+		assertThat(clientRegistrationResult.getRedirectUris())
+				.containsExactlyInAnyOrderElementsOf(registeredClient.getRedirectUris());
+
+		List<String> grantTypes = new ArrayList<>();
+		registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
+				grantTypes.add(authorizationGrantType.getValue()));
+		assertThat(clientRegistrationResult.getGrantTypes()).containsExactlyInAnyOrderElementsOf(grantTypes);
+
+		assertThat(clientRegistrationResult.getResponseTypes())
+				.containsExactly(OAuth2AuthorizationResponseType.CODE.getValue());
+		assertThat(clientRegistrationResult.getScopes())
+				.containsExactlyInAnyOrderElementsOf(registeredClient.getScopes());
+		assertThat(clientRegistrationResult.getTokenEndpointAuthenticationMethod())
+				.isEqualTo(registeredClient.getClientAuthenticationMethods().iterator().next().getValue());
+		assertThat(clientRegistrationResult.getTokenEndpointAuthenticationSigningAlgorithm())
+				.isEqualTo(registeredClient.getClientSettings().getTokenEndpointAuthenticationSigningAlgorithm().getName());
+		assertThat(clientRegistrationResult.getJwkSetUrl().toString())
+				.isEqualTo(registeredClient.getClientSettings().getJwkSetUrl());
+		assertThat(clientRegistrationResult.getIdTokenSignedResponseAlgorithm())
+				.isEqualTo(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName());
+
+		AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
+		String expectedRegistrationClientUrl = UriComponentsBuilder.fromUriString(authorizationServerContext.getIssuer())
+				.path(authorizationServerContext.getAuthorizationServerSettings().getOidcClientRegistrationEndpoint())
+				.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()).toUriString();
+
+		assertThat(clientRegistrationResult.getRegistrationClientUrl().toString()).isEqualTo(expectedRegistrationClientUrl);
+		assertThat(clientRegistrationResult.getRegistrationAccessToken()).isNull();
+	}
+
+	private static Jwt createJwtClientConfiguration() {
+		return createJwt(Collections.singleton("client.read"));
+	}
+
+	private static Jwt createJwt(Set<String> scopes) {
+		// @formatter:off
+		JwsHeader jwsHeader = TestJwsHeaders.jwsHeader()
+				.build();
+		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet()
+				.claim(OAuth2ParameterNames.SCOPE, scopes)
+				.build();
+		Jwt jwt = Jwt.withTokenValue("jwt-access-token")
+				.headers(headers -> headers.putAll(jwsHeader.getHeaders()))
+				.claims(claims -> claims.putAll(jwtClaimsSet.getClaims()))
+				.build();
+		// @formatter:on
+		return jwt;
+	}
+
+}

+ 7 - 205
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java

@@ -58,7 +58,6 @@ import org.springframework.security.oauth2.server.authorization.context.TestAuth
 import org.springframework.security.oauth2.server.authorization.oidc.OidcClientMetadataClaimNames;
 import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
 import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
-import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
 import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
 import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
 import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
@@ -72,7 +71,6 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -225,7 +223,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 	}
 
 	@Test
-	public void authenticateWhenClientRegistrationRequestAndAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() {
+	public void authenticateWhenAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() {
 		Jwt jwt = createJwt(Collections.singleton("unauthorized.scope"));
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -255,7 +253,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 	}
 
 	@Test
-	public void authenticateWhenClientRegistrationRequestAndAccessTokenContainsRequiredScopeAndAdditionalScopeThenThrowOAuth2AuthenticationException() {
+	public void authenticateWhenAccessTokenContainsRequiredScopeAndAdditionalScopeThenThrowOAuth2AuthenticationException() {
 		Jwt jwt = createJwt(new HashSet<>(Arrays.asList("client.create", "scope1")));
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -285,7 +283,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 	}
 
 	@Test
-	public void authenticateWhenClientRegistrationRequestAndInvalidRedirectUriThenThrowOAuth2AuthenticationException() {
+	public void authenticateWhenInvalidRedirectUriThenThrowOAuth2AuthenticationException() {
 		Jwt jwt = createJwtClientRegistration();
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -320,7 +318,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 	}
 
 	@Test
-	public void authenticateWhenClientRegistrationRequestAndRedirectUriContainsFragmentThenThrowOAuth2AuthenticationException() {
+	public void authenticateWhenRedirectUriContainsFragmentThenThrowOAuth2AuthenticationException() {
 		Jwt jwt = createJwtClientRegistration();
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -355,7 +353,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 	}
 
 	@Test
-	public void authenticateWhenClientRegistrationRequestAndInvalidTokenEndpointAuthenticationMethodThenThrowOAuth2AuthenticationException() {
+	public void authenticateWhenInvalidTokenEndpointAuthenticationMethodThenThrowOAuth2AuthenticationException() {
 		Jwt jwt = createJwtClientRegistration();
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -434,7 +432,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 	}
 
 	@Test
-	public void authenticateWhenClientRegistrationRequestAndTokenEndpointAuthenticationSigningAlgorithmNotProvidedThenDefaults() {
+	public void authenticateWhenTokenEndpointAuthenticationSigningAlgorithmNotProvidedThenDefaults() {
 		Jwt jwt = createJwtClientRegistration();
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -521,7 +519,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 	}
 
 	@Test
-	public void authenticateWhenClientRegistrationRequestAndValidAccessTokenThenReturnClientRegistration() {
+	public void authenticateWhenValidAccessTokenThenReturnClientRegistration() {
 		Jwt jwt = createJwtClientRegistration();
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -622,202 +620,6 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 		assertThat(clientRegistrationResult.getRegistrationAccessToken()).isEqualTo(jwt.getTokenValue());
 	}
 
-	@Test
-	public void authenticateWhenClientConfigurationRequestAndAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() {
-		Jwt jwt = createJwt(Collections.singleton("unauthorized.scope"));
-		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
-				jwt.getTokenValue(), jwt.getIssuedAt(),
-				jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
-		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
-		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
-				registeredClient, jwtAccessToken, jwt.getClaims()).build();
-		when(this.authorizationService.findByToken(
-				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
-				.thenReturn(authorization);
-
-		JwtAuthenticationToken principal = new JwtAuthenticationToken(
-				jwt, AuthorityUtils.createAuthorityList("SCOPE_unauthorized.scope"));
-
-		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, registeredClient.getClientId());
-
-		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
-				.isInstanceOf(OAuth2AuthenticationException.class)
-				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
-				.isEqualTo(OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
-		verify(this.authorizationService).findByToken(
-				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
-	}
-
-	@Test
-	public void authenticateWhenClientConfigurationRequestAndAccessTokenContainsRequiredScopeAndAdditionalScopeThenThrowOAuth2AuthenticationException() {
-		Jwt jwt = createJwt(new HashSet<>(Arrays.asList("client.read", "scope1")));
-		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
-				jwt.getTokenValue(), jwt.getIssuedAt(),
-				jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
-		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
-		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
-				registeredClient, jwtAccessToken, jwt.getClaims()).build();
-		when(this.authorizationService.findByToken(
-				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
-				.thenReturn(authorization);
-
-		JwtAuthenticationToken principal = new JwtAuthenticationToken(
-				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read", "SCOPE_scope1"));
-
-		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, registeredClient.getClientId());
-
-		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
-				.isInstanceOf(OAuth2AuthenticationException.class)
-				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
-				.isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
-		verify(this.authorizationService).findByToken(
-				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
-	}
-
-	@Test
-	public void authenticateWhenClientConfigurationRequestAndRegisteredClientNotFoundThenThrowOAuth2AuthenticationException() {
-		Jwt jwt = createJwtClientConfiguration();
-		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
-				jwt.getTokenValue(), jwt.getIssuedAt(),
-				jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
-		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
-		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
-				registeredClient, jwtAccessToken, jwt.getClaims()).build();
-		when(this.authorizationService.findByToken(
-				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
-				.thenReturn(authorization);
-
-		JwtAuthenticationToken principal = new JwtAuthenticationToken(
-				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
-
-		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, registeredClient.getClientId());
-
-		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
-				.isInstanceOf(OAuth2AuthenticationException.class)
-				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
-				.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
-		verify(this.authorizationService).findByToken(
-				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
-		verify(this.registeredClientRepository).findByClientId(
-				eq(registeredClient.getClientId()));
-	}
-
-	@Test
-	public void authenticateWhenClientConfigurationRequestClientIdNotEqualToAuthorizedClientThenThrowOAuth2AuthenticationException() {
-		Jwt jwt = createJwtClientConfiguration();
-		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
-				jwt.getTokenValue(), jwt.getIssuedAt(),
-				jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
-		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
-		RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient()
-				.id("registration-2").clientId("client-2").build();
-		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
-				authorizedRegisteredClient, jwtAccessToken, jwt.getClaims()).build();
-		when(this.authorizationService.findByToken(
-				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
-				.thenReturn(authorization);
-		when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
-				.thenReturn(registeredClient);
-
-		JwtAuthenticationToken principal = new JwtAuthenticationToken(
-				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
-
-		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, registeredClient.getClientId());
-
-		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
-				.isInstanceOf(OAuth2AuthenticationException.class)
-				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
-				.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
-		verify(this.authorizationService).findByToken(
-				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
-		verify(this.registeredClientRepository).findByClientId(
-				eq(registeredClient.getClientId()));
-	}
-
-	@Test
-	public void authenticateWhenClientConfigurationRequestAndValidAccessTokenThenReturnClientRegistration() {
-		Jwt jwt = createJwtClientConfiguration();
-		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
-				jwt.getTokenValue(), jwt.getIssuedAt(),
-				jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
-		RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
-				.clientAuthenticationMethods((clientAuthenticationMethods) -> {
-					clientAuthenticationMethods.clear();
-					clientAuthenticationMethods.add(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
-				})
-				.clientSettings(
-						ClientSettings.builder()
-								.tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS512)
-								.jwkSetUrl("https://client.example.com/jwks")
-								.build()
-				)
-				.build();
-		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
-				registeredClient, jwtAccessToken, jwt.getClaims()).build();
-		when(this.authorizationService.findByToken(
-				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
-				.thenReturn(authorization);
-		when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
-				.thenReturn(registeredClient);
-
-		JwtAuthenticationToken principal = new JwtAuthenticationToken(
-				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
-
-		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, registeredClient.getClientId());
-
-		OidcClientRegistrationAuthenticationToken authenticationResult =
-				(OidcClientRegistrationAuthenticationToken) this.authenticationProvider.authenticate(authentication);
-
-		verify(this.authorizationService).findByToken(
-				eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
-		verify(this.registeredClientRepository).findByClientId(
-				eq(registeredClient.getClientId()));
-
-		// verify that the "registration" access token is not invalidated after it is used
-		verify(this.authorizationService, never()).save(eq(authorization));
-		assertThat(authorization.getAccessToken().isInvalidated()).isFalse();
-
-		OidcClientRegistration clientRegistrationResult = authenticationResult.getClientRegistration();
-		assertThat(clientRegistrationResult.getClientId()).isEqualTo(registeredClient.getClientId());
-		assertThat(clientRegistrationResult.getClientIdIssuedAt()).isEqualTo(registeredClient.getClientIdIssuedAt());
-		assertThat(clientRegistrationResult.getClientSecret()).isEqualTo(registeredClient.getClientSecret());
-		assertThat(clientRegistrationResult.getClientSecretExpiresAt()).isEqualTo(registeredClient.getClientSecretExpiresAt());
-		assertThat(clientRegistrationResult.getClientName()).isEqualTo(registeredClient.getClientName());
-		assertThat(clientRegistrationResult.getRedirectUris())
-				.containsExactlyInAnyOrderElementsOf(registeredClient.getRedirectUris());
-
-		List<String> grantTypes = new ArrayList<>();
-		registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
-				grantTypes.add(authorizationGrantType.getValue()));
-		assertThat(clientRegistrationResult.getGrantTypes()).containsExactlyInAnyOrderElementsOf(grantTypes);
-
-		assertThat(clientRegistrationResult.getResponseTypes())
-				.containsExactly(OAuth2AuthorizationResponseType.CODE.getValue());
-		assertThat(clientRegistrationResult.getScopes())
-				.containsExactlyInAnyOrderElementsOf(registeredClient.getScopes());
-		assertThat(clientRegistrationResult.getTokenEndpointAuthenticationMethod())
-				.isEqualTo(registeredClient.getClientAuthenticationMethods().iterator().next().getValue());
-		assertThat(clientRegistrationResult.getTokenEndpointAuthenticationSigningAlgorithm())
-				.isEqualTo(registeredClient.getClientSettings().getTokenEndpointAuthenticationSigningAlgorithm().getName());
-		assertThat(clientRegistrationResult.getJwkSetUrl().toString())
-				.isEqualTo(registeredClient.getClientSettings().getJwkSetUrl());
-		assertThat(clientRegistrationResult.getIdTokenSignedResponseAlgorithm())
-				.isEqualTo(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName());
-
-		AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
-		String expectedRegistrationClientUrl = UriComponentsBuilder.fromUriString(authorizationServerContext.getIssuer())
-				.path(authorizationServerContext.getAuthorizationServerSettings().getOidcClientRegistrationEndpoint())
-				.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()).toUriString();
-
-		assertThat(clientRegistrationResult.getRegistrationClientUrl().toString()).isEqualTo(expectedRegistrationClientUrl);
-		assertThat(clientRegistrationResult.getRegistrationAccessToken()).isNull();
-	}
-
 	private static Jwt createJwtClientRegistration() {
 		return createJwt(Collections.singleton("client.create"));
 	}