Browse Source

Extract OIDC client configuration implementation

Closes gh-941
Joe Grandja 2 years ago
parent
commit
72804be45b
10 changed files with 846 additions and 405 deletions
  1. 2 1
      docs/src/docs/asciidoc/protocol-endpoints.adoc
  2. 7 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java
  3. 147 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java
  4. 99 172
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java
  5. 1 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java
  6. 89 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationConverter.java
  7. 16 27
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java
  8. 78 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcClientRegistrationAuthenticationConverter.java
  9. 400 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProviderTests.java
  10. 7 205
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java

+ 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.
 

+ 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 javax.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);
+	}
+
+}

+ 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"));
 	}