diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesMapper.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesMapper.java new file mode 100644 index 0000000000..0ff1d412d4 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesMapper.java @@ -0,0 +1,146 @@ +/* + * Copyright 2012-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.boot.autoconfigure.security.oauth2.client; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Provider; +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.convert.ApplicationConversionService; +import org.springframework.core.convert.ConversionException; +import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistration.Builder; +import org.springframework.security.oauth2.client.registration.ClientRegistrations; +import org.springframework.security.oauth2.core.AuthenticationMethod; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.util.StringUtils; + +/** + * Maps {@link OAuth2ClientProperties} to {@link ClientRegistration ClientRegistrations}. + * + * @author Phillip Webb + * @author Thiago Hirata + * @author Madhura Bhave + * @author MyeongHyeon Lee + * @author Andy Wilkinson + * @since 3.1.0 + */ +public final class OAuth2ClientPropertiesMapper { + + private final OAuth2ClientProperties properties; + + /** + * Creates a new mapper for the given {@code properties}. + * @param properties the properties to map + */ + public OAuth2ClientPropertiesMapper(OAuth2ClientProperties properties) { + this.properties = properties; + } + + /** + * Maps the properties to {@link ClientRegistration ClientRegistrations}. + * @return the mapped {@code ClientRegistrations} + */ + public Map asClientRegistrations() { + Map clientRegistrations = new HashMap<>(); + this.properties.getRegistration() + .forEach((key, value) -> clientRegistrations.put(key, + getClientRegistration(key, value, this.properties.getProvider()))); + return clientRegistrations; + } + + private static ClientRegistration getClientRegistration(String registrationId, + OAuth2ClientProperties.Registration properties, Map providers) { + Builder builder = getBuilderFromIssuerIfPossible(registrationId, properties.getProvider(), providers); + if (builder == null) { + builder = getBuilder(registrationId, properties.getProvider(), providers); + } + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getClientId).to(builder::clientId); + map.from(properties::getClientSecret).to(builder::clientSecret); + map.from(properties::getClientAuthenticationMethod) + .as(ClientAuthenticationMethod::new) + .to(builder::clientAuthenticationMethod); + map.from(properties::getAuthorizationGrantType) + .as(AuthorizationGrantType::new) + .to(builder::authorizationGrantType); + map.from(properties::getRedirectUri).to(builder::redirectUri); + map.from(properties::getScope).as(StringUtils::toStringArray).to(builder::scope); + map.from(properties::getClientName).to(builder::clientName); + return builder.build(); + } + + private static Builder getBuilderFromIssuerIfPossible(String registrationId, String configuredProviderId, + Map providers) { + String providerId = (configuredProviderId != null) ? configuredProviderId : registrationId; + if (providers.containsKey(providerId)) { + Provider provider = providers.get(providerId); + String issuer = provider.getIssuerUri(); + if (issuer != null) { + Builder builder = ClientRegistrations.fromIssuerLocation(issuer).registrationId(registrationId); + return getBuilder(builder, provider); + } + } + return null; + } + + private static Builder getBuilder(String registrationId, String configuredProviderId, + Map providers) { + String providerId = (configuredProviderId != null) ? configuredProviderId : registrationId; + CommonOAuth2Provider provider = getCommonProvider(providerId); + if (provider == null && !providers.containsKey(providerId)) { + throw new IllegalStateException(getErrorMessage(configuredProviderId, registrationId)); + } + Builder builder = (provider != null) ? provider.getBuilder(registrationId) + : ClientRegistration.withRegistrationId(registrationId); + if (providers.containsKey(providerId)) { + return getBuilder(builder, providers.get(providerId)); + } + return builder; + } + + private static String getErrorMessage(String configuredProviderId, String registrationId) { + return ((configuredProviderId != null) ? "Unknown provider ID '" + configuredProviderId + "'" + : "Provider ID must be specified for client registration '" + registrationId + "'"); + } + + private static Builder getBuilder(Builder builder, Provider provider) { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(provider::getAuthorizationUri).to(builder::authorizationUri); + map.from(provider::getTokenUri).to(builder::tokenUri); + map.from(provider::getUserInfoUri).to(builder::userInfoUri); + map.from(provider::getUserInfoAuthenticationMethod) + .as(AuthenticationMethod::new) + .to(builder::userInfoAuthenticationMethod); + map.from(provider::getJwkSetUri).to(builder::jwkSetUri); + map.from(provider::getUserNameAttribute).to(builder::userNameAttributeName); + return builder; + } + + private static CommonOAuth2Provider getCommonProvider(String providerId) { + try { + return ApplicationConversionService.getSharedInstance().convert(providerId, CommonOAuth2Provider.class); + } + catch (ConversionException ex) { + return null; + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java index 905602259d..343f511caf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java @@ -16,21 +16,9 @@ package org.springframework.boot.autoconfigure.security.oauth2.client; -import java.util.HashMap; import java.util.Map; -import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Provider; -import org.springframework.boot.context.properties.PropertyMapper; -import org.springframework.boot.convert.ApplicationConversionService; -import org.springframework.core.convert.ConversionException; -import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistration.Builder; -import org.springframework.security.oauth2.client.registration.ClientRegistrations; -import org.springframework.security.oauth2.core.AuthenticationMethod; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.util.StringUtils; /** * Adapter class to convert {@link OAuth2ClientProperties} to a @@ -41,95 +29,17 @@ import org.springframework.util.StringUtils; * @author Madhura Bhave * @author MyeongHyeon Lee * @since 2.1.0 + * @deprecated since 3.1.0 for removal in 3.3.0 in favor of + * {@link OAuth2ClientPropertiesMapper} */ +@Deprecated(since = "3.1.0", forRemoval = true) public final class OAuth2ClientPropertiesRegistrationAdapter { private OAuth2ClientPropertiesRegistrationAdapter() { } public static Map getClientRegistrations(OAuth2ClientProperties properties) { - Map clientRegistrations = new HashMap<>(); - properties.getRegistration() - .forEach((key, value) -> clientRegistrations.put(key, - getClientRegistration(key, value, properties.getProvider()))); - return clientRegistrations; - } - - private static ClientRegistration getClientRegistration(String registrationId, - OAuth2ClientProperties.Registration properties, Map providers) { - Builder builder = getBuilderFromIssuerIfPossible(registrationId, properties.getProvider(), providers); - if (builder == null) { - builder = getBuilder(registrationId, properties.getProvider(), providers); - } - PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - map.from(properties::getClientId).to(builder::clientId); - map.from(properties::getClientSecret).to(builder::clientSecret); - map.from(properties::getClientAuthenticationMethod) - .as(ClientAuthenticationMethod::new) - .to(builder::clientAuthenticationMethod); - map.from(properties::getAuthorizationGrantType) - .as(AuthorizationGrantType::new) - .to(builder::authorizationGrantType); - map.from(properties::getRedirectUri).to(builder::redirectUri); - map.from(properties::getScope).as(StringUtils::toStringArray).to(builder::scope); - map.from(properties::getClientName).to(builder::clientName); - return builder.build(); - } - - private static Builder getBuilderFromIssuerIfPossible(String registrationId, String configuredProviderId, - Map providers) { - String providerId = (configuredProviderId != null) ? configuredProviderId : registrationId; - if (providers.containsKey(providerId)) { - Provider provider = providers.get(providerId); - String issuer = provider.getIssuerUri(); - if (issuer != null) { - Builder builder = ClientRegistrations.fromIssuerLocation(issuer).registrationId(registrationId); - return getBuilder(builder, provider); - } - } - return null; - } - - private static Builder getBuilder(String registrationId, String configuredProviderId, - Map providers) { - String providerId = (configuredProviderId != null) ? configuredProviderId : registrationId; - CommonOAuth2Provider provider = getCommonProvider(providerId); - if (provider == null && !providers.containsKey(providerId)) { - throw new IllegalStateException(getErrorMessage(configuredProviderId, registrationId)); - } - Builder builder = (provider != null) ? provider.getBuilder(registrationId) - : ClientRegistration.withRegistrationId(registrationId); - if (providers.containsKey(providerId)) { - return getBuilder(builder, providers.get(providerId)); - } - return builder; - } - - private static String getErrorMessage(String configuredProviderId, String registrationId) { - return ((configuredProviderId != null) ? "Unknown provider ID '" + configuredProviderId + "'" - : "Provider ID must be specified for client registration '" + registrationId + "'"); - } - - private static Builder getBuilder(Builder builder, Provider provider) { - PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - map.from(provider::getAuthorizationUri).to(builder::authorizationUri); - map.from(provider::getTokenUri).to(builder::tokenUri); - map.from(provider::getUserInfoUri).to(builder::userInfoUri); - map.from(provider::getUserInfoAuthenticationMethod) - .as(AuthenticationMethod::new) - .to(builder::userInfoAuthenticationMethod); - map.from(provider::getJwkSetUri).to(builder::jwkSetUri); - map.from(provider::getUserNameAttribute).to(builder::userNameAttributeName); - return builder; - } - - private static CommonOAuth2Provider getCommonProvider(String providerId) { - try { - return ApplicationConversionService.getSharedInstance().convert(providerId, CommonOAuth2Provider.class); - } - catch (ConversionException ex) { - return null; - } + return new OAuth2ClientPropertiesMapper(properties).asClientRegistrations(); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientConfigurations.java index 53cd4dde19..8eb3871b93 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientConfigurations.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientConfigurations.java @@ -24,7 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; -import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -55,7 +55,7 @@ class ReactiveOAuth2ClientConfigurations { @Bean InMemoryReactiveClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) { List registrations = new ArrayList<>( - OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values()); + new OAuth2ClientPropertiesMapper(properties).asClientRegistrations().values()); return new InMemoryReactiveClientRegistrationRepository(registrations); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfiguration.java index 62c3502079..f05762dba4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfiguration.java @@ -22,7 +22,7 @@ import java.util.List; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; -import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; @@ -46,7 +46,7 @@ class OAuth2ClientRegistrationRepositoryConfiguration { @ConditionalOnMissingBean(ClientRegistrationRepository.class) InMemoryClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) { List registrations = new ArrayList<>( - OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values()); + new OAuth2ClientPropertiesMapper(properties).asClientRegistrations().values()); return new InMemoryClientRegistrationRepository(registrations); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapterTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesMapperTests.java similarity index 94% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapterTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesMapperTests.java index cb629a67b9..3bcb4663a4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapterTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesMapperTests.java @@ -43,14 +43,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** - * Tests for {@link OAuth2ClientPropertiesRegistrationAdapter}. + * Tests for {@link OAuth2ClientPropertiesMapper}. * * @author Phillip Webb * @author Madhura Bhave * @author Thiago Hirata * @author HaiTao Zhang */ -class OAuth2ClientPropertiesRegistrationAdapterTests { +class OAuth2ClientPropertiesMapperTests { private MockWebServer server; @@ -70,8 +70,8 @@ class OAuth2ClientPropertiesRegistrationAdapterTests { registration.setClientName("clientName"); properties.getRegistration().put("registration", registration); properties.getProvider().put("provider", provider); - Map registrations = OAuth2ClientPropertiesRegistrationAdapter - .getClientRegistrations(properties); + Map registrations = new OAuth2ClientPropertiesMapper(properties) + .asClientRegistrations(); ClientRegistration adapted = registrations.get("registration"); ProviderDetails adaptedProvider = adapted.getProviderDetails(); assertThat(adaptedProvider.getAuthorizationUri()).isEqualTo("https://example.com/auth"); @@ -102,8 +102,8 @@ class OAuth2ClientPropertiesRegistrationAdapterTests { registration.setClientId("clientId"); registration.setClientSecret("clientSecret"); properties.getRegistration().put("registration", registration); - Map registrations = OAuth2ClientPropertiesRegistrationAdapter - .getClientRegistrations(properties); + Map registrations = new OAuth2ClientPropertiesMapper(properties) + .asClientRegistrations(); ClientRegistration adapted = registrations.get("registration"); ProviderDetails adaptedProvider = adapted.getProviderDetails(); assertThat(adaptedProvider.getAuthorizationUri()).isEqualTo("https://accounts.google.com/o/oauth2/v2/auth"); @@ -130,8 +130,8 @@ class OAuth2ClientPropertiesRegistrationAdapterTests { OAuth2ClientProperties.Registration registration = createRegistration("google"); registration.setClientName("clientName"); properties.getRegistration().put("registration", registration); - Map registrations = OAuth2ClientPropertiesRegistrationAdapter - .getClientRegistrations(properties); + Map registrations = new OAuth2ClientPropertiesMapper(properties) + .asClientRegistrations(); ClientRegistration adapted = registrations.get("registration"); ProviderDetails adaptedProvider = adapted.getProviderDetails(); assertThat(adaptedProvider.getAuthorizationUri()).isEqualTo("https://accounts.google.com/o/oauth2/v2/auth"); @@ -161,7 +161,7 @@ class OAuth2ClientPropertiesRegistrationAdapterTests { registration.setProvider("missing"); properties.getRegistration().put("registration", registration); assertThatIllegalStateException() - .isThrownBy(() -> OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties)) + .isThrownBy(() -> new OAuth2ClientPropertiesMapper(properties).asClientRegistrations()) .withMessageContaining("Unknown provider ID 'missing'"); } @@ -172,8 +172,8 @@ class OAuth2ClientPropertiesRegistrationAdapterTests { registration.setClientId("clientId"); registration.setClientSecret("clientSecret"); properties.getRegistration().put("google", registration); - Map registrations = OAuth2ClientPropertiesRegistrationAdapter - .getClientRegistrations(properties); + Map registrations = new OAuth2ClientPropertiesMapper(properties) + .asClientRegistrations(); ClientRegistration adapted = registrations.get("google"); ProviderDetails adaptedProvider = adapted.getProviderDetails(); assertThat(adaptedProvider.getAuthorizationUri()).isEqualTo("https://accounts.google.com/o/oauth2/v2/auth"); @@ -201,7 +201,7 @@ class OAuth2ClientPropertiesRegistrationAdapterTests { OAuth2ClientProperties.Registration registration = new OAuth2ClientProperties.Registration(); properties.getRegistration().put("missing", registration); assertThatIllegalStateException() - .isThrownBy(() -> OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties)) + .isThrownBy(() -> new OAuth2ClientPropertiesMapper(properties).asClientRegistrations()) .withMessageContaining("Provider ID must be specified for client registration 'missing'"); } @@ -250,8 +250,8 @@ class OAuth2ClientPropertiesRegistrationAdapterTests { OAuth2ClientProperties properties = new OAuth2ClientProperties(); properties.getProvider().put("okta-oidc", provider); properties.getRegistration().put("okta", registration); - Map registrations = OAuth2ClientPropertiesRegistrationAdapter - .getClientRegistrations(properties); + Map registrations = new OAuth2ClientPropertiesMapper(properties) + .asClientRegistrations(); ClientRegistration adapted = registrations.get("okta"); ProviderDetails providerDetails = adapted.getProviderDetails(); assertThat(adapted.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_POST); @@ -301,8 +301,8 @@ class OAuth2ClientPropertiesRegistrationAdapterTests { provider.setIssuerUri(issuer); properties.getProvider().put(providerId, provider); properties.getRegistration().put("okta", registration); - Map registrations = OAuth2ClientPropertiesRegistrationAdapter - .getClientRegistrations(properties); + Map registrations = new OAuth2ClientPropertiesMapper(properties) + .asClientRegistrations(); ClientRegistration adapted = registrations.get("okta"); ProviderDetails providerDetails = adapted.getProviderDetails(); assertThat(adapted.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);