فهرست منبع

Adds ability to inject custom metadata at client registration

Closes gh-1172
Dmitriy Dubson 2 سال پیش
والد
کامیت
3de6a7dfd1

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

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2020-2022 the original author or authors.
+ * Copyright 2020-2023 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@ 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.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
 import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
+import org.springframework.security.oauth2.server.authorization.oidc.converter.RegisteredClientOidcClientRegistrationConverter;
 import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
 import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
 import org.springframework.util.StringUtils;
 import org.springframework.util.StringUtils;
@@ -46,11 +47,13 @@ import org.springframework.util.StringUtils;
  * @author Ovidiu Popa
  * @author Ovidiu Popa
  * @author Joe Grandja
  * @author Joe Grandja
  * @author Rafal Lewczuk
  * @author Rafal Lewczuk
+ * @author Dmitriy Dubson
  * @since 0.4.0
  * @since 0.4.0
  * @see RegisteredClientRepository
  * @see RegisteredClientRepository
  * @see OAuth2AuthorizationService
  * @see OAuth2AuthorizationService
  * @see OidcClientRegistrationAuthenticationToken
  * @see OidcClientRegistrationAuthenticationToken
  * @see OidcClientRegistrationAuthenticationProvider
  * @see OidcClientRegistrationAuthenticationProvider
+ * @see RegisteredClientOidcClientRegistrationConverter
  * @see <a href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientConfigurationEndpoint">4. Client Configuration Endpoint</a>
  * @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 {
 public final class OidcClientConfigurationAuthenticationProvider implements AuthenticationProvider {
@@ -58,7 +61,7 @@ public final class OidcClientConfigurationAuthenticationProvider implements Auth
 	private final Log logger = LogFactory.getLog(getClass());
 	private final Log logger = LogFactory.getLog(getClass());
 	private final RegisteredClientRepository registeredClientRepository;
 	private final RegisteredClientRepository registeredClientRepository;
 	private final OAuth2AuthorizationService authorizationService;
 	private final OAuth2AuthorizationService authorizationService;
-	private final Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter;
+	private Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter;
 
 
 	/**
 	/**
 	 * Constructs an {@code OidcClientConfigurationAuthenticationProvider} using the provided parameters.
 	 * Constructs an {@code OidcClientConfigurationAuthenticationProvider} using the provided parameters.
@@ -75,6 +78,17 @@ public final class OidcClientConfigurationAuthenticationProvider implements Auth
 		this.clientRegistrationConverter = new RegisteredClientOidcClientRegistrationConverter();
 		this.clientRegistrationConverter = new RegisteredClientOidcClientRegistrationConverter();
 	}
 	}
 
 
+	/**
+	 * Sets the {@link Converter} used for converting an {@link RegisteredClient} to a {@link OidcClientRegistration}.
+	 *
+	 * @param clientRegistrationConverter the {@link Converter} used for converting an {@link RegisteredClient} to a {@link OidcClientRegistration}
+	 * @since 1.2.0
+	 */
+	public void setClientRegistrationConverter(Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter) {
+		Assert.notNull(clientRegistrationConverter, "clientRegistrationConverter cannot be null");
+		this.clientRegistrationConverter = clientRegistrationConverter;
+	}
+
 	@Override
 	@Override
 	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 		OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication =
 		OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication =

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

@@ -17,15 +17,12 @@ package org.springframework.security.oauth2.server.authorization.oidc.authentica
 
 
 import java.net.URI;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URISyntaxException;
-import java.time.Instant;
-import java.util.Base64;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
-import java.util.UUID;
 
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.commons.logging.LogFactory;
@@ -35,8 +32,6 @@ import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.crypto.factory.PasswordEncoderFactories;
 import org.springframework.security.crypto.factory.PasswordEncoderFactories;
-import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
-import org.springframework.security.crypto.keygen.StringKeyGenerator;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.ClaimAccessor;
 import org.springframework.security.oauth2.core.ClaimAccessor;
@@ -46,7 +41,6 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.OAuth2Token;
 import org.springframework.security.oauth2.core.OAuth2Token;
-import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
 import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
 import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
 import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
@@ -59,8 +53,8 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
 import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
 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.OidcClientMetadataClaimNames;
 import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
 import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
-import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
-import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
+import org.springframework.security.oauth2.server.authorization.oidc.converter.OidcClientRegistrationRegisteredClientConverter;
+import org.springframework.security.oauth2.server.authorization.oidc.converter.RegisteredClientOidcClientRegistrationConverter;
 import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
 import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
 import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
 import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
 import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
 import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
@@ -75,6 +69,7 @@ import org.springframework.util.StringUtils;
  * @author Ovidiu Popa
  * @author Ovidiu Popa
  * @author Joe Grandja
  * @author Joe Grandja
  * @author Rafal Lewczuk
  * @author Rafal Lewczuk
+ * @author Dmitriy Dubson
  * @since 0.1.1
  * @since 0.1.1
  * @see RegisteredClientRepository
  * @see RegisteredClientRepository
  * @see OAuth2AuthorizationService
  * @see OAuth2AuthorizationService
@@ -91,7 +86,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 	private final RegisteredClientRepository registeredClientRepository;
 	private final RegisteredClientRepository registeredClientRepository;
 	private final OAuth2AuthorizationService authorizationService;
 	private final OAuth2AuthorizationService authorizationService;
 	private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
 	private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
-	private final Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter;
+	private Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter;
 	private Converter<OidcClientRegistration, RegisteredClient> registeredClientConverter;
 	private Converter<OidcClientRegistration, RegisteredClient> registeredClientConverter;
 	private PasswordEncoder passwordEncoder;
 	private PasswordEncoder passwordEncoder;
 
 
@@ -172,6 +167,17 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 		this.registeredClientConverter = registeredClientConverter;
 		this.registeredClientConverter = registeredClientConverter;
 	}
 	}
 
 
+	/**
+	 * Sets the {@link Converter} used for converting an {@link RegisteredClient} to a {@link OidcClientRegistration}.
+	 *
+	 * @param clientRegistrationConverter the {@link Converter} used for converting an {@link RegisteredClient} to a {@link OidcClientRegistration}
+	 * @since 1.2.0
+	 */
+	public void setClientRegistrationConverter(Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter) {
+		Assert.notNull(clientRegistrationConverter, "clientRegistrationConverter cannot be null");
+		this.clientRegistrationConverter = clientRegistrationConverter;
+	}
+
 	/**
 	/**
 	 * Sets the {@link PasswordEncoder} used to encode the {@link RegisteredClient#getClientSecret() client secret}.
 	 * Sets the {@link PasswordEncoder} used to encode the {@link RegisteredClient#getClientSecret() client secret}.
 	 * If not set, the client secret will be encoded using {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}.
 	 * If not set, the client secret will be encoded using {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}.
@@ -368,89 +374,4 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 		throw new OAuth2AuthenticationException(error);
 		throw new OAuth2AuthenticationException(error);
 	}
 	}
 
 
-	private static final class OidcClientRegistrationRegisteredClientConverter 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()));
-
-			if (!CollectionUtils.isEmpty(clientRegistration.getPostLogoutRedirectUris())) {
-				builder.postLogoutRedirectUris(postLogoutRedirectUris ->
-						postLogoutRedirectUris.addAll(clientRegistration.getPostLogoutRedirectUris()));
-			}
-
-			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()));
-			}
-
-			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;
-				}
-				clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(signatureAlgorithm);
-				clientSettingsBuilder.jwkSetUrl(clientRegistration.getJwkSetUrl().toString());
-			}
-
-			builder
-					.clientSettings(clientSettingsBuilder.build())
-					.tokenSettings(TokenSettings.builder()
-							.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
-							.build());
-
-			return builder.build();
-			// @formatter:on
-		}
-
-	}
 }
 }

+ 126 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/OidcClientRegistrationRegisteredClientConverter.java

@@ -0,0 +1,126 @@
+/*
+ * Copyright 2020-2023 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.converter;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
+import org.springframework.security.crypto.keygen.StringKeyGenerator;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
+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.oidc.OidcClientRegistration;
+import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
+import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
+import org.springframework.util.CollectionUtils;
+
+import java.time.Instant;
+import java.util.Base64;
+import java.util.UUID;
+
+/**
+ * @author Joe Grandja
+ * @author Dmitriy Dubson
+ * @since 1.2.0
+ */
+public final class OidcClientRegistrationRegisteredClientConverter 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()));
+
+		if (!CollectionUtils.isEmpty(clientRegistration.getPostLogoutRedirectUris())) {
+			builder.postLogoutRedirectUris(postLogoutRedirectUris ->
+					postLogoutRedirectUris.addAll(clientRegistration.getPostLogoutRedirectUris()));
+		}
+
+		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()));
+		}
+
+		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;
+			}
+			clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(signatureAlgorithm);
+			clientSettingsBuilder.jwkSetUrl(clientRegistration.getJwkSetUrl().toString());
+		}
+
+		builder
+				.clientSettings(clientSettingsBuilder.build())
+				.tokenSettings(TokenSettings.builder()
+						.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
+						.build());
+
+		return builder.build();
+		// @formatter:on
+	}
+
+}
+

+ 3 - 3
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/RegisteredClientOidcClientRegistrationConverter.java → oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/RegisteredClientOidcClientRegistrationConverter.java

@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-package org.springframework.security.oauth2.server.authorization.oidc.authentication;
+package org.springframework.security.oauth2.server.authorization.oidc.converter;
 
 
 import org.springframework.core.convert.converter.Converter;
 import org.springframework.core.convert.converter.Converter;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
@@ -29,9 +29,9 @@ import org.springframework.web.util.UriComponentsBuilder;
 
 
 /**
 /**
  * @author Joe Grandja
  * @author Joe Grandja
- * @since 0.4.0
+ * @since 1.2.0
  */
  */
-final class RegisteredClientOidcClientRegistrationConverter implements Converter<RegisteredClient, OidcClientRegistration> {
+public final class RegisteredClientOidcClientRegistrationConverter implements Converter<RegisteredClient, OidcClientRegistration> {
 
 
 	@Override
 	@Override
 	public OidcClientRegistration convert(RegisteredClient registeredClient) {
 	public OidcClientRegistration convert(RegisteredClient registeredClient) {

+ 76 - 112
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java

@@ -17,11 +17,13 @@ package org.springframework.security.oauth2.server.authorization.config.annotati
 
 
 import java.time.Instant;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.time.temporal.ChronoUnit;
-import java.util.Base64;
 import java.util.Collections;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.List;
-import java.util.UUID;
+import java.util.Map;
 import java.util.function.Consumer;
 import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 
 import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jose.jwk.source.JWKSource;
 import com.nimbusds.jose.jwk.source.JWKSource;
@@ -59,8 +61,6 @@ import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.crypto.factory.PasswordEncoderFactories;
 import org.springframework.security.crypto.factory.PasswordEncoderFactories;
-import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
-import org.springframework.security.crypto.keygen.StringKeyGenerator;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
@@ -71,7 +71,6 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
 import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
 import org.springframework.security.oauth2.jose.TestJwks;
 import org.springframework.security.oauth2.jose.TestJwks;
-import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
 import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
 import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
 import org.springframework.security.oauth2.jwt.JwsHeader;
 import org.springframework.security.oauth2.jwt.JwsHeader;
 import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.jwt.Jwt;
@@ -92,11 +91,12 @@ import org.springframework.security.oauth2.server.authorization.oidc.OidcClientR
 import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider;
 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.OidcClientRegistrationAuthenticationProvider;
 import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.oidc.converter.OidcClientRegistrationRegisteredClientConverter;
+import org.springframework.security.oauth2.server.authorization.oidc.converter.RegisteredClientOidcClientRegistrationConverter;
 import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter;
 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.oauth2.server.authorization.oidc.web.authentication.OidcClientRegistrationAuthenticationConverter;
 import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
 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.settings.ClientSettings;
-import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
 import org.springframework.security.oauth2.server.authorization.test.SpringTestContext;
 import org.springframework.security.oauth2.server.authorization.test.SpringTestContext;
 import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension;
 import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.SecurityFilterChain;
@@ -131,6 +131,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
  *
  *
  * @author Ovidiu Popa
  * @author Ovidiu Popa
  * @author Joe Grandja
  * @author Joe Grandja
+ * @author Dmitriy Dubson
  */
  */
 @ExtendWith(SpringTestContextExtension.class)
 @ExtendWith(SpringTestContextExtension.class)
 public class OidcClientRegistrationTests {
 public class OidcClientRegistrationTests {
@@ -420,6 +421,7 @@ public class OidcClientRegistrationTests {
 				.scope("scope2")
 				.scope("scope2")
 				.claim("custom-metadata-name-1", "value-1")
 				.claim("custom-metadata-name-1", "value-1")
 				.claim("custom-metadata-name-2", "value-2")
 				.claim("custom-metadata-name-2", "value-2")
+				.claim("non-registered-custom-metadata", "value-3")
 				.build();
 				.build();
 		// @formatter:on
 		// @formatter:on
 
 
@@ -428,10 +430,15 @@ public class OidcClientRegistrationTests {
 		RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
 		RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
 				clientRegistrationResponse.getClientId());
 				clientRegistrationResponse.getClientId());
 
 
+		assertThat(clientRegistrationResponse.<String>getClaim("custom-metadata-name-1")).isEqualTo("value-1");
+		assertThat(clientRegistrationResponse.<String>getClaim("custom-metadata-name-2")).isEqualTo("value-2");
+		assertThat(clientRegistrationResponse.<String>getClaim("non-registered-custom-metadata")).isNull();
+
 		assertThat(registeredClient.getClientSettings().<String>getSetting("custom-metadata-name-1"))
 		assertThat(registeredClient.getClientSettings().<String>getSetting("custom-metadata-name-1"))
 				.isEqualTo("value-1");
 				.isEqualTo("value-1");
 		assertThat(registeredClient.getClientSettings().<String>getSetting("custom-metadata-name-2"))
 		assertThat(registeredClient.getClientSettings().<String>getSetting("custom-metadata-name-2"))
 				.isEqualTo("value-2");
 				.isEqualTo("value-2");
+		assertThat(registeredClient.getClientSettings().<String>getSetting("non-registered-custom-metadata")).isNull();
 	}
 	}
 
 
 	private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception {
 	private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception {
@@ -581,7 +588,7 @@ public class OidcClientRegistrationTests {
 					oidc
 					oidc
 						.clientRegistrationEndpoint(clientRegistration ->
 						.clientRegistrationEndpoint(clientRegistration ->
 							clientRegistration
 							clientRegistration
-								.authenticationProviders(configureRegisteredClientConverter())
+								.authenticationProviders(configureRegisteredClientConverters())
 						)
 						)
 				);
 				);
 			RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
 			RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
@@ -600,111 +607,18 @@ public class OidcClientRegistrationTests {
 		}
 		}
 		// @formatter:on
 		// @formatter:on
 
 
-		private Consumer<List<AuthenticationProvider>> configureRegisteredClientConverter() {
-			return (authenticationProviders) -> {
-				authenticationProviders.forEach((authenticationProvider) -> {
-					if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider) {
-						((OidcClientRegistrationAuthenticationProvider) authenticationProvider)
-								.setRegisteredClientConverter(new OidcClientRegistrationRegisteredClientConverter());
-					}
-				});
-			};
-		}
-
-		// NOTE:
-		// This is a copy of OidcClientRegistrationAuthenticationProvider.OidcClientRegistrationRegisteredClientConverter
-		// with a minor enhancement supporting custom metadata claims.
-		private static final class OidcClientRegistrationRegisteredClientConverter 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()));
-
-				if (!CollectionUtils.isEmpty(clientRegistration.getPostLogoutRedirectUris())) {
-					builder.postLogoutRedirectUris(postLogoutRedirectUris ->
-							postLogoutRedirectUris.addAll(clientRegistration.getPostLogoutRedirectUris()));
-				}
-
-				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()));
-				}
-
-				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;
-					}
-					clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(signatureAlgorithm);
-					clientSettingsBuilder.jwkSetUrl(clientRegistration.getJwkSetUrl().toString());
-				}
-
-				// Add custom metadata claims
-				clientRegistration.getClaims().forEach((claim, value) -> {
-					if (claim.startsWith("custom-metadata")) {
-						clientSettingsBuilder.setting(claim, value);
-					}
-				});
-
-				builder
-						.clientSettings(clientSettingsBuilder.build())
-						.tokenSettings(TokenSettings.builder()
-								.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
-								.build());
-
-				return builder.build();
-				// @formatter:on
-			}
-
+		private Consumer<List<AuthenticationProvider>> configureRegisteredClientConverters() {
+            // @formatter:off
+			return (authenticationProviders) ->
+					authenticationProviders.forEach(authenticationProvider -> {
+						List<String> customClientMetadata = List.of("custom-metadata-name-1", "custom-metadata-name-2");
+
+						if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider provider) {
+							provider.setRegisteredClientConverter(new CustomRegisteredClientConverter(customClientMetadata));
+							provider.setClientRegistrationConverter(new CustomClientRegistrationConverter(customClientMetadata));
+						}
+					});
+			// @formatter:on
 		}
 		}
 
 
 	}
 	}
@@ -781,4 +695,54 @@ public class OidcClientRegistrationTests {
 
 
 	}
 	}
 
 
+	static class CustomClientRegistrationConverter implements Converter<RegisteredClient, OidcClientRegistration> {
+		private final List<String> customMetadata;
+
+		private final RegisteredClientOidcClientRegistrationConverter delegate;
+
+		CustomClientRegistrationConverter(List<String> customMetadata) {
+			this.customMetadata = customMetadata;
+			this.delegate = new RegisteredClientOidcClientRegistrationConverter();
+		}
+
+		public OidcClientRegistration convert(RegisteredClient registeredClient) {
+			var clientRegistration = delegate.convert(registeredClient);
+			Map<String, Object> claims = new HashMap<>(clientRegistration.getClaims());
+			if (!CollectionUtils.isEmpty(customMetadata)) {
+				ClientSettings clientSettings = registeredClient.getClientSettings();
+
+				claims.putAll(customMetadata.stream()
+						.filter(metadatum -> clientSettings.getSetting(metadatum) != null)
+						.collect(Collectors.toMap(Function.identity(), clientSettings::getSetting)));
+			}
+			return OidcClientRegistration.withClaims(claims).build();
+		}
+	}
+
+	static class CustomRegisteredClientConverter implements Converter<OidcClientRegistration, RegisteredClient> {
+		private final List<String> customMetadata;
+
+		private final OidcClientRegistrationRegisteredClientConverter delegate;
+
+		CustomRegisteredClientConverter(List<String> customMetadata) {
+			this.customMetadata = customMetadata;
+			this.delegate = new OidcClientRegistrationRegisteredClientConverter();
+		}
+
+		public RegisteredClient convert(OidcClientRegistration clientRegistration) {
+			RegisteredClient convertedClient = delegate.convert(clientRegistration);
+			ClientSettings.Builder clientSettingsBuilder = ClientSettings
+					.withSettings(convertedClient.getClientSettings().getSettings());
+
+			if (!CollectionUtils.isEmpty(this.customMetadata)) {
+				clientRegistration.getClaims().forEach((claim, value) -> {
+					if (this.customMetadata.contains(claim)) {
+						clientSettingsBuilder.setting(claim, value);
+					}
+				});
+			}
+
+			return RegisteredClient.from(convertedClient).clientSettings(clientSettingsBuilder.build()).build();
+		}
+	}
 }
 }

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

@@ -378,6 +378,13 @@ public class OidcClientConfigurationAuthenticationProviderTests {
 		assertThat(clientRegistrationResult.getRegistrationAccessToken()).isNull();
 		assertThat(clientRegistrationResult.getRegistrationAccessToken()).isNull();
 	}
 	}
 
 
+	@Test
+	public void setClientRegistrationConverterWhenNullThenThrowIllegalArgumentException() {
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> this.authenticationProvider.setClientRegistrationConverter(null))
+				.withMessage("clientRegistrationConverter cannot be null");
+	}
+
 	private static Jwt createJwtClientConfiguration() {
 	private static Jwt createJwtClientConfiguration() {
 		return createJwt(Collections.singleton("client.read"));
 		return createJwt(Collections.singleton("client.read"));
 	}
 	}

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

@@ -158,6 +158,13 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 				.withMessage("registeredClientConverter cannot be null");
 				.withMessage("registeredClientConverter cannot be null");
 	}
 	}
 
 
+	@Test
+	public void setClientRegistrationConverterWhenNullThenThrowIllegalArgumentException() {
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> this.authenticationProvider.setClientRegistrationConverter(null))
+				.withMessage("clientRegistrationConverter cannot be null");
+	}
+
 	@Test
 	@Test
 	public void setPasswordEncoderWhenNullThenThrowIllegalArgumentException() {
 	public void setPasswordEncoderWhenNullThenThrowIllegalArgumentException() {
 		assertThatThrownBy(() -> this.authenticationProvider.setPasswordEncoder(null))
 		assertThatThrownBy(() -> this.authenticationProvider.setPasswordEncoder(null))