Create spring-boot-security-oauth2-client module
This commit is contained in:
committed by
Phillip Webb
parent
f53b9786e5
commit
d5e84c627d
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
|
||||
/**
|
||||
* Condition that matches if any {@code spring.security.oauth2.client.registration}
|
||||
* properties are defined.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class ClientsConfiguredCondition extends SpringBootCondition {
|
||||
|
||||
private static final Bindable<Map<String, OAuth2ClientProperties.Registration>> STRING_REGISTRATION_MAP = Bindable
|
||||
.mapOf(String.class, OAuth2ClientProperties.Registration.class);
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||
ConditionMessage.Builder message = ConditionMessage.forCondition("OAuth2 Clients Configured Condition");
|
||||
Map<String, OAuth2ClientProperties.Registration> registrations = getRegistrations(context.getEnvironment());
|
||||
if (!registrations.isEmpty()) {
|
||||
return ConditionOutcome.match(message.foundExactly("registered clients " + registrations.values()
|
||||
.stream()
|
||||
.map(OAuth2ClientProperties.Registration::getClientId)
|
||||
.collect(Collectors.joining(", "))));
|
||||
}
|
||||
return ConditionOutcome.noMatch(message.notAvailable("registered clients"));
|
||||
}
|
||||
|
||||
private Map<String, OAuth2ClientProperties.Registration> getRegistrations(Environment environment) {
|
||||
return Binder.get(environment)
|
||||
.bind("spring.security.oauth2.client.registration", STRING_REGISTRATION_MAP)
|
||||
.orElse(Collections.emptyMap());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
|
||||
/**
|
||||
* Condition that matches if any {@code spring.security.oauth2.client.registration}
|
||||
* properties are defined.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Documented
|
||||
@Conditional(ClientsConfiguredCondition.class)
|
||||
public @interface ConditionalOnOAuth2ClientRegistrationProperties {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
|
||||
import org.springframework.boot.security.oauth2.client.autoconfigure.OAuth2ClientAutoConfiguration.NonReactiveWebApplicationCondition;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for OAuth client support.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
* @since 3.5.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@Conditional(NonReactiveWebApplicationCondition.class)
|
||||
@ConditionalOnClass(ClientRegistration.class)
|
||||
@Import({ OAuth2ClientConfigurations.ClientRegistrationRepositoryConfiguration.class,
|
||||
OAuth2ClientConfigurations.OAuth2AuthorizedClientServiceConfiguration.class })
|
||||
public class OAuth2ClientAutoConfiguration {
|
||||
|
||||
static class NonReactiveWebApplicationCondition extends NoneNestedConditions {
|
||||
|
||||
NonReactiveWebApplicationCondition() {
|
||||
super(ConfigurationPhase.PARSE_CONFIGURATION);
|
||||
}
|
||||
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
||||
static class ReactiveWebApplicationCondition {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||
|
||||
/**
|
||||
* Configurations related to auto-configuration of OAuth2 client support.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class OAuth2ClientConfigurations {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnOAuth2ClientRegistrationProperties
|
||||
@EnableConfigurationProperties(OAuth2ClientProperties.class)
|
||||
@ConditionalOnMissingBean(ClientRegistrationRepository.class)
|
||||
static class ClientRegistrationRepositoryConfiguration {
|
||||
|
||||
@Bean
|
||||
InMemoryClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) {
|
||||
List<ClientRegistration> registrations = new ArrayList<>(
|
||||
new OAuth2ClientPropertiesMapper(properties).asClientRegistrations().values());
|
||||
return new InMemoryClientRegistrationRepository(registrations);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnBean(ClientRegistrationRepository.class)
|
||||
static class OAuth2AuthorizedClientServiceConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
OAuth2AuthorizedClientService authorizedClientService(
|
||||
ClientRegistrationRepository clientRegistrationRepository) {
|
||||
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* OAuth 2.0 client properties.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
* @author Artsiom Yudovin
|
||||
* @author MyeongHyeon Lee
|
||||
* @author Moritz Halbritter
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@ConfigurationProperties("spring.security.oauth2.client")
|
||||
public class OAuth2ClientProperties implements InitializingBean {
|
||||
|
||||
/**
|
||||
* OAuth provider details.
|
||||
*/
|
||||
private final Map<String, Provider> provider = new HashMap<>();
|
||||
|
||||
/**
|
||||
* OAuth client registrations.
|
||||
*/
|
||||
private final Map<String, Registration> registration = new HashMap<>();
|
||||
|
||||
public Map<String, Provider> getProvider() {
|
||||
return this.provider;
|
||||
}
|
||||
|
||||
public Map<String, Registration> getRegistration() {
|
||||
return this.registration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
validate();
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
getRegistration().forEach(this::validateRegistration);
|
||||
}
|
||||
|
||||
private void validateRegistration(String id, Registration registration) {
|
||||
if (!StringUtils.hasText(registration.getClientId())) {
|
||||
throw new IllegalStateException("Client id of registration '%s' must not be empty.".formatted(id));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A single client registration.
|
||||
*/
|
||||
public static class Registration {
|
||||
|
||||
/**
|
||||
* Reference to the OAuth 2.0 provider to use. May reference an element from the
|
||||
* 'provider' property or used one of the commonly used providers (google, github,
|
||||
* facebook, okta).
|
||||
*/
|
||||
private String provider;
|
||||
|
||||
/**
|
||||
* Client ID for the registration.
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* Client secret of the registration.
|
||||
*/
|
||||
private String clientSecret;
|
||||
|
||||
/**
|
||||
* Client authentication method. May be left blank when using a pre-defined
|
||||
* provider.
|
||||
*/
|
||||
private String clientAuthenticationMethod;
|
||||
|
||||
/**
|
||||
* Authorization grant type. May be left blank when using a pre-defined provider.
|
||||
*/
|
||||
private String authorizationGrantType;
|
||||
|
||||
/**
|
||||
* Redirect URI. May be left blank when using a pre-defined provider.
|
||||
*/
|
||||
private String redirectUri;
|
||||
|
||||
/**
|
||||
* Authorization scopes. When left blank the provider's default scopes, if any,
|
||||
* will be used.
|
||||
*/
|
||||
private Set<String> scope;
|
||||
|
||||
/**
|
||||
* Client name. May be left blank when using a pre-defined provider.
|
||||
*/
|
||||
private String clientName;
|
||||
|
||||
public String getProvider() {
|
||||
return this.provider;
|
||||
}
|
||||
|
||||
public void setProvider(String provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getClientSecret() {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public String getClientAuthenticationMethod() {
|
||||
return this.clientAuthenticationMethod;
|
||||
}
|
||||
|
||||
public void setClientAuthenticationMethod(String clientAuthenticationMethod) {
|
||||
this.clientAuthenticationMethod = clientAuthenticationMethod;
|
||||
}
|
||||
|
||||
public String getAuthorizationGrantType() {
|
||||
return this.authorizationGrantType;
|
||||
}
|
||||
|
||||
public void setAuthorizationGrantType(String authorizationGrantType) {
|
||||
this.authorizationGrantType = authorizationGrantType;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return this.redirectUri;
|
||||
}
|
||||
|
||||
public void setRedirectUri(String redirectUri) {
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public Set<String> getScope() {
|
||||
return this.scope;
|
||||
}
|
||||
|
||||
public void setScope(Set<String> scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public String getClientName() {
|
||||
return this.clientName;
|
||||
}
|
||||
|
||||
public void setClientName(String clientName) {
|
||||
this.clientName = clientName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Provider {
|
||||
|
||||
/**
|
||||
* Authorization URI for the provider.
|
||||
*/
|
||||
private String authorizationUri;
|
||||
|
||||
/**
|
||||
* Token URI for the provider.
|
||||
*/
|
||||
private String tokenUri;
|
||||
|
||||
/**
|
||||
* User info URI for the provider.
|
||||
*/
|
||||
private String userInfoUri;
|
||||
|
||||
/**
|
||||
* User info authentication method for the provider.
|
||||
*/
|
||||
private String userInfoAuthenticationMethod;
|
||||
|
||||
/**
|
||||
* Name of the attribute that will be used to extract the username from the call
|
||||
* to 'userInfoUri'.
|
||||
*/
|
||||
private String userNameAttribute;
|
||||
|
||||
/**
|
||||
* JWK set URI for the provider.
|
||||
*/
|
||||
private String jwkSetUri;
|
||||
|
||||
/**
|
||||
* URI that can either be an OpenID Connect discovery endpoint or an OAuth 2.0
|
||||
* Authorization Server Metadata endpoint defined by RFC 8414.
|
||||
*/
|
||||
private String issuerUri;
|
||||
|
||||
public String getAuthorizationUri() {
|
||||
return this.authorizationUri;
|
||||
}
|
||||
|
||||
public void setAuthorizationUri(String authorizationUri) {
|
||||
this.authorizationUri = authorizationUri;
|
||||
}
|
||||
|
||||
public String getTokenUri() {
|
||||
return this.tokenUri;
|
||||
}
|
||||
|
||||
public void setTokenUri(String tokenUri) {
|
||||
this.tokenUri = tokenUri;
|
||||
}
|
||||
|
||||
public String getUserInfoUri() {
|
||||
return this.userInfoUri;
|
||||
}
|
||||
|
||||
public void setUserInfoUri(String userInfoUri) {
|
||||
this.userInfoUri = userInfoUri;
|
||||
}
|
||||
|
||||
public String getUserInfoAuthenticationMethod() {
|
||||
return this.userInfoAuthenticationMethod;
|
||||
}
|
||||
|
||||
public void setUserInfoAuthenticationMethod(String userInfoAuthenticationMethod) {
|
||||
this.userInfoAuthenticationMethod = userInfoAuthenticationMethod;
|
||||
}
|
||||
|
||||
public String getUserNameAttribute() {
|
||||
return this.userNameAttribute;
|
||||
}
|
||||
|
||||
public void setUserNameAttribute(String userNameAttribute) {
|
||||
this.userNameAttribute = userNameAttribute;
|
||||
}
|
||||
|
||||
public String getJwkSetUri() {
|
||||
return this.jwkSetUri;
|
||||
}
|
||||
|
||||
public void setJwkSetUri(String jwkSetUri) {
|
||||
this.jwkSetUri = jwkSetUri;
|
||||
}
|
||||
|
||||
public String getIssuerUri() {
|
||||
return this.issuerUri;
|
||||
}
|
||||
|
||||
public void setIssuerUri(String issuerUri) {
|
||||
this.issuerUri = issuerUri;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.convert.ApplicationConversionService;
|
||||
import org.springframework.boot.security.oauth2.client.autoconfigure.OAuth2ClientProperties.Provider;
|
||||
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 4.0.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<String, ClientRegistration> asClientRegistrations() {
|
||||
Map<String, ClientRegistration> 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<String, Provider> 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<String, Provider> 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<String, Provider> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Support for Spring Security's OAuth 2 client.
|
||||
*/
|
||||
package org.springframework.boot.security.oauth2.client.autoconfigure;
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure.reactive;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security's Reactive
|
||||
* OAuth2 client.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@Conditional(ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition.class)
|
||||
@ConditionalOnClass({ Flux.class, ClientRegistration.class })
|
||||
@Import({ ReactiveOAuth2ClientConfigurations.ReactiveClientRegistrationRepositoryConfiguration.class,
|
||||
ReactiveOAuth2ClientConfigurations.ReactiveOAuth2AuthorizedClientServiceConfiguration.class })
|
||||
public class ReactiveOAuth2ClientAutoConfiguration {
|
||||
|
||||
static class NonServletApplicationCondition extends NoneNestedConditions {
|
||||
|
||||
NonServletApplicationCondition() {
|
||||
super(ConfigurationPhase.PARSE_CONFIGURATION);
|
||||
}
|
||||
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
static class ServletApplicationCondition {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure.reactive;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.security.oauth2.client.autoconfigure.ConditionalOnOAuth2ClientRegistrationProperties;
|
||||
import org.springframework.boot.security.oauth2.client.autoconfigure.OAuth2ClientProperties;
|
||||
import org.springframework.boot.security.oauth2.client.autoconfigure.OAuth2ClientPropertiesMapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
|
||||
/**
|
||||
* Reactive OAuth2 Client configurations.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class ReactiveOAuth2ClientConfigurations {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(OAuth2ClientProperties.class)
|
||||
@ConditionalOnOAuth2ClientRegistrationProperties
|
||||
@ConditionalOnMissingBean(ReactiveClientRegistrationRepository.class)
|
||||
static class ReactiveClientRegistrationRepositoryConfiguration {
|
||||
|
||||
@Bean
|
||||
InMemoryReactiveClientRegistrationRepository reactiveClientRegistrationRepository(
|
||||
OAuth2ClientProperties properties) {
|
||||
List<ClientRegistration> registrations = new ArrayList<>(
|
||||
new OAuth2ClientPropertiesMapper(properties).asClientRegistrations().values());
|
||||
return new InMemoryReactiveClientRegistrationRepository(registrations);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnBean(ReactiveClientRegistrationRepository.class)
|
||||
static class ReactiveOAuth2AuthorizedClientServiceConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
ReactiveOAuth2AuthorizedClientService reactiveAuthorizedClientService(
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository) {
|
||||
return new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure.reactive;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.security.autoconfigure.reactive.ReactiveSecurityAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
* Auto-configuration for reactive web security that uses an OAuth 2 client.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class,
|
||||
after = ReactiveOAuth2ClientAutoConfiguration.class)
|
||||
@ConditionalOnClass({ Flux.class, EnableWebFluxSecurity.class, ServerOAuth2AuthorizedClientRepository.class })
|
||||
@ConditionalOnBean(ReactiveOAuth2AuthorizedClientService.class)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
||||
public class ReactiveOAuth2ClientWebSecurityAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository(
|
||||
ReactiveOAuth2AuthorizedClientService authorizedClientService) {
|
||||
return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
http.authorizeExchange((exchange) -> exchange.anyExchange().authenticated());
|
||||
http.oauth2Login(withDefaults());
|
||||
http.oauth2Client(withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for Spring Security's Reactive OAuth 2 client.
|
||||
*/
|
||||
package org.springframework.boot.security.oauth2.client.autoconfigure.reactive;
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure.servlet;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.security.autoconfigure.ConditionalOnDefaultWebSecurity;
|
||||
import org.springframework.boot.security.autoconfigure.servlet.SecurityAutoConfiguration;
|
||||
import org.springframework.boot.security.oauth2.client.autoconfigure.OAuth2ClientAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
* Auto-configuration for web security that uses an OAuth 2 client.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @since 3.5.0
|
||||
*/
|
||||
@AutoConfiguration(before = SecurityAutoConfiguration.class, after = OAuth2ClientAutoConfiguration.class)
|
||||
@ConditionalOnClass({ EnableWebSecurity.class, OAuth2AuthorizedClientRepository.class })
|
||||
@ConditionalOnBean(OAuth2AuthorizedClientService.class)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
public class OAuth2ClientWebSecurityAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
OAuth2AuthorizedClientRepository authorizedClientRepository(OAuth2AuthorizedClientService authorizedClientService) {
|
||||
return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnDefaultWebSecurity
|
||||
static class OAuth2SecurityFilterChainConfiguration {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
|
||||
http.oauth2Login(withDefaults());
|
||||
http.oauth2Client(withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for Spring Security's OAuth 2 client.
|
||||
*/
|
||||
package org.springframework.boot.security.oauth2.client.autoconfigure.servlet;
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"groups": [],
|
||||
"properties": []
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
org.springframework.boot.security.oauth2.client.autoconfigure.OAuth2ClientAutoConfiguration
|
||||
org.springframework.boot.security.oauth2.client.autoconfigure.reactive.ReactiveOAuth2ClientAutoConfiguration
|
||||
org.springframework.boot.security.oauth2.client.autoconfigure.reactive.ReactiveOAuth2ClientWebSecurityAutoConfiguration
|
||||
org.springframework.boot.security.oauth2.client.autoconfigure.servlet.OAuth2ClientWebSecurityAutoConfiguration
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2ClientAutoConfiguration}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class OAuth2ClientAutoConfigurationTests {
|
||||
|
||||
private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration";
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(OAuth2ClientAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void beansShouldNotBeCreatedWhenPropertiesAbsent() {
|
||||
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ClientRegistrationRepository.class)
|
||||
.doesNotHaveBean(OAuth2AuthorizedClientService.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void beansAreCreatedWhenPropertiesPresent() {
|
||||
this.contextRunner
|
||||
.withPropertyValues(REGISTRATION_PREFIX + ".foo.client-id=abcd",
|
||||
REGISTRATION_PREFIX + ".foo.client-secret=secret", REGISTRATION_PREFIX + ".foo.provider=github")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ClientRegistrationRepository.class);
|
||||
assertThat(context).hasSingleBean(OAuth2AuthorizedClientService.class);
|
||||
ClientRegistrationRepository repository = context.getBean(ClientRegistrationRepository.class);
|
||||
ClientRegistration registration = repository.findByRegistrationId("foo");
|
||||
assertThat(registration).isNotNull();
|
||||
assertThat(registration.getClientSecret()).isEqualTo("secret");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void clientServiceBeanIsConditionalOnMissingBean() {
|
||||
this.contextRunner
|
||||
.withBean("testAuthorizedClientService", OAuth2AuthorizedClientService.class,
|
||||
() -> mock(OAuth2AuthorizedClientService.class))
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(OAuth2AuthorizedClientService.class);
|
||||
assertThat(context).hasBean("testAuthorizedClientService");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void clientServiceBeanIsCreatedWithUserDefinedClientRegistrationRepository() {
|
||||
this.contextRunner
|
||||
.withBean(ClientRegistrationRepository.class,
|
||||
() -> new InMemoryClientRegistrationRepository(getClientRegistration("test", "test")))
|
||||
.run((context) -> assertThat(context).hasSingleBean(OAuth2AuthorizedClientService.class));
|
||||
}
|
||||
|
||||
private ClientRegistration getClientRegistration(String id, String userInfoUri) {
|
||||
ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(id);
|
||||
builder.clientName("foo")
|
||||
.clientId("foo")
|
||||
.clientAuthenticationMethod(
|
||||
org.springframework.security.oauth2.core.ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.scope("read")
|
||||
.clientSecret("secret")
|
||||
.redirectUri("https://redirect-uri.com")
|
||||
.authorizationUri("https://authorization-uri.com")
|
||||
.tokenUri("https://token-uri.com")
|
||||
.userInfoUri(userInfoUri)
|
||||
.userNameAttributeName("login");
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.security.oauth2.client.autoconfigure.OAuth2ClientProperties.Provider;
|
||||
import org.springframework.boot.security.oauth2.client.autoconfigure.OAuth2ClientProperties.Registration;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails.UserInfoEndpoint;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2ClientPropertiesMapper}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
* @author Thiago Hirata
|
||||
* @author HaiTao Zhang
|
||||
*/
|
||||
class OAuth2ClientPropertiesMapperTests {
|
||||
|
||||
private MockWebServer server;
|
||||
|
||||
@AfterEach
|
||||
void cleanup() throws Exception {
|
||||
if (this.server != null) {
|
||||
this.server.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClientRegistrationsWhenUsingDefinedProviderShouldAdapt() {
|
||||
OAuth2ClientProperties properties = new OAuth2ClientProperties();
|
||||
Provider provider = createProvider();
|
||||
provider.setUserInfoAuthenticationMethod("form");
|
||||
OAuth2ClientProperties.Registration registration = createRegistration("provider");
|
||||
registration.setClientName("clientName");
|
||||
properties.getRegistration().put("registration", registration);
|
||||
properties.getProvider().put("provider", provider);
|
||||
Map<String, ClientRegistration> registrations = new OAuth2ClientPropertiesMapper(properties)
|
||||
.asClientRegistrations();
|
||||
ClientRegistration adapted = registrations.get("registration");
|
||||
ProviderDetails adaptedProvider = adapted.getProviderDetails();
|
||||
assertThat(adaptedProvider.getAuthorizationUri()).isEqualTo("https://example.com/auth");
|
||||
assertThat(adaptedProvider.getTokenUri()).isEqualTo("https://example.com/token");
|
||||
UserInfoEndpoint userInfoEndpoint = adaptedProvider.getUserInfoEndpoint();
|
||||
assertThat(userInfoEndpoint.getUri()).isEqualTo("https://example.com/info");
|
||||
assertThat(userInfoEndpoint.getAuthenticationMethod())
|
||||
.isEqualTo(org.springframework.security.oauth2.core.AuthenticationMethod.FORM);
|
||||
assertThat(userInfoEndpoint.getUserNameAttributeName()).isEqualTo("sub");
|
||||
assertThat(adaptedProvider.getJwkSetUri()).isEqualTo("https://example.com/jwk");
|
||||
assertThat(adapted.getRegistrationId()).isEqualTo("registration");
|
||||
assertThat(adapted.getClientId()).isEqualTo("clientId");
|
||||
assertThat(adapted.getClientSecret()).isEqualTo("clientSecret");
|
||||
assertThat(adapted.getClientAuthenticationMethod())
|
||||
.isEqualTo(org.springframework.security.oauth2.core.ClientAuthenticationMethod.CLIENT_SECRET_POST);
|
||||
assertThat(adapted.getAuthorizationGrantType())
|
||||
.isEqualTo(org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
assertThat(adapted.getRedirectUri()).isEqualTo("https://example.com/redirect");
|
||||
assertThat(adapted.getScopes()).containsExactly("user");
|
||||
assertThat(adapted.getClientName()).isEqualTo("clientName");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClientRegistrationsWhenUsingCommonProviderShouldAdapt() {
|
||||
OAuth2ClientProperties properties = new OAuth2ClientProperties();
|
||||
OAuth2ClientProperties.Registration registration = new OAuth2ClientProperties.Registration();
|
||||
registration.setProvider("google");
|
||||
registration.setClientId("clientId");
|
||||
registration.setClientSecret("clientSecret");
|
||||
properties.getRegistration().put("registration", registration);
|
||||
Map<String, ClientRegistration> 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");
|
||||
assertThat(adaptedProvider.getTokenUri()).isEqualTo("https://www.googleapis.com/oauth2/v4/token");
|
||||
UserInfoEndpoint userInfoEndpoint = adaptedProvider.getUserInfoEndpoint();
|
||||
assertThat(userInfoEndpoint.getUri()).isEqualTo("https://www.googleapis.com/oauth2/v3/userinfo");
|
||||
assertThat(userInfoEndpoint.getUserNameAttributeName()).isEqualTo(IdTokenClaimNames.SUB);
|
||||
assertThat(adaptedProvider.getJwkSetUri()).isEqualTo("https://www.googleapis.com/oauth2/v3/certs");
|
||||
assertThat(adapted.getRegistrationId()).isEqualTo("registration");
|
||||
assertThat(adapted.getClientId()).isEqualTo("clientId");
|
||||
assertThat(adapted.getClientSecret()).isEqualTo("clientSecret");
|
||||
assertThat(adapted.getClientAuthenticationMethod())
|
||||
.isEqualTo(org.springframework.security.oauth2.core.ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
|
||||
assertThat(adapted.getAuthorizationGrantType())
|
||||
.isEqualTo(org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
assertThat(adapted.getRedirectUri()).isEqualTo("{baseUrl}/{action}/oauth2/code/{registrationId}");
|
||||
assertThat(adapted.getScopes()).containsExactly("openid", "profile", "email");
|
||||
assertThat(adapted.getClientName()).isEqualTo("Google");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClientRegistrationsWhenUsingCommonProviderWithOverrideShouldAdapt() {
|
||||
OAuth2ClientProperties properties = new OAuth2ClientProperties();
|
||||
OAuth2ClientProperties.Registration registration = createRegistration("google");
|
||||
registration.setClientName("clientName");
|
||||
properties.getRegistration().put("registration", registration);
|
||||
Map<String, ClientRegistration> 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");
|
||||
assertThat(adaptedProvider.getTokenUri()).isEqualTo("https://www.googleapis.com/oauth2/v4/token");
|
||||
UserInfoEndpoint userInfoEndpoint = adaptedProvider.getUserInfoEndpoint();
|
||||
assertThat(userInfoEndpoint.getUri()).isEqualTo("https://www.googleapis.com/oauth2/v3/userinfo");
|
||||
assertThat(userInfoEndpoint.getUserNameAttributeName()).isEqualTo(IdTokenClaimNames.SUB);
|
||||
assertThat(userInfoEndpoint.getAuthenticationMethod())
|
||||
.isEqualTo(org.springframework.security.oauth2.core.AuthenticationMethod.HEADER);
|
||||
assertThat(adaptedProvider.getJwkSetUri()).isEqualTo("https://www.googleapis.com/oauth2/v3/certs");
|
||||
assertThat(adapted.getRegistrationId()).isEqualTo("registration");
|
||||
assertThat(adapted.getClientId()).isEqualTo("clientId");
|
||||
assertThat(adapted.getClientSecret()).isEqualTo("clientSecret");
|
||||
assertThat(adapted.getClientAuthenticationMethod())
|
||||
.isEqualTo(org.springframework.security.oauth2.core.ClientAuthenticationMethod.CLIENT_SECRET_POST);
|
||||
assertThat(adapted.getAuthorizationGrantType())
|
||||
.isEqualTo(org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
assertThat(adapted.getRedirectUri()).isEqualTo("https://example.com/redirect");
|
||||
assertThat(adapted.getScopes()).containsExactly("user");
|
||||
assertThat(adapted.getClientName()).isEqualTo("clientName");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClientRegistrationsWhenUnknownProviderShouldThrowException() {
|
||||
OAuth2ClientProperties properties = new OAuth2ClientProperties();
|
||||
OAuth2ClientProperties.Registration registration = new OAuth2ClientProperties.Registration();
|
||||
registration.setProvider("missing");
|
||||
properties.getRegistration().put("registration", registration);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> new OAuth2ClientPropertiesMapper(properties).asClientRegistrations())
|
||||
.withMessageContaining("Unknown provider ID 'missing'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClientRegistrationsWhenProviderNotSpecifiedShouldUseRegistrationId() {
|
||||
OAuth2ClientProperties properties = new OAuth2ClientProperties();
|
||||
OAuth2ClientProperties.Registration registration = new OAuth2ClientProperties.Registration();
|
||||
registration.setClientId("clientId");
|
||||
registration.setClientSecret("clientSecret");
|
||||
properties.getRegistration().put("google", registration);
|
||||
Map<String, ClientRegistration> 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");
|
||||
assertThat(adaptedProvider.getTokenUri()).isEqualTo("https://www.googleapis.com/oauth2/v4/token");
|
||||
UserInfoEndpoint userInfoEndpoint = adaptedProvider.getUserInfoEndpoint();
|
||||
assertThat(userInfoEndpoint.getUri()).isEqualTo("https://www.googleapis.com/oauth2/v3/userinfo");
|
||||
assertThat(userInfoEndpoint.getAuthenticationMethod())
|
||||
.isEqualTo(org.springframework.security.oauth2.core.AuthenticationMethod.HEADER);
|
||||
assertThat(adaptedProvider.getJwkSetUri()).isEqualTo("https://www.googleapis.com/oauth2/v3/certs");
|
||||
assertThat(adapted.getRegistrationId()).isEqualTo("google");
|
||||
assertThat(adapted.getClientId()).isEqualTo("clientId");
|
||||
assertThat(adapted.getClientSecret()).isEqualTo("clientSecret");
|
||||
assertThat(adapted.getClientAuthenticationMethod())
|
||||
.isEqualTo(org.springframework.security.oauth2.core.ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
|
||||
assertThat(adapted.getAuthorizationGrantType())
|
||||
.isEqualTo(org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
assertThat(adapted.getRedirectUri()).isEqualTo("{baseUrl}/{action}/oauth2/code/{registrationId}");
|
||||
assertThat(adapted.getScopes()).containsExactly("openid", "profile", "email");
|
||||
assertThat(adapted.getClientName()).isEqualTo("Google");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClientRegistrationsWhenProviderNotSpecifiedAndUnknownProviderShouldThrowException() {
|
||||
OAuth2ClientProperties properties = new OAuth2ClientProperties();
|
||||
OAuth2ClientProperties.Registration registration = new OAuth2ClientProperties.Registration();
|
||||
properties.getRegistration().put("missing", registration);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> new OAuth2ClientPropertiesMapper(properties).asClientRegistrations())
|
||||
.withMessageContaining("Provider ID must be specified for client registration 'missing'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void oidcProviderConfigurationWhenProviderNotSpecifiedOnRegistration() throws Exception {
|
||||
Registration login = new OAuth2ClientProperties.Registration();
|
||||
login.setClientId("clientId");
|
||||
login.setClientSecret("clientSecret");
|
||||
testIssuerConfiguration(login, "okta", 0, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void oidcProviderConfigurationWhenProviderSpecifiedOnRegistration() throws Exception {
|
||||
OAuth2ClientProperties.Registration login = new Registration();
|
||||
login.setProvider("okta-oidc");
|
||||
login.setClientId("clientId");
|
||||
login.setClientSecret("clientSecret");
|
||||
testIssuerConfiguration(login, "okta-oidc", 0, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void issuerUriConfigurationTriesOidcRfc8414UriSecond() throws Exception {
|
||||
OAuth2ClientProperties.Registration login = new Registration();
|
||||
login.setClientId("clientId");
|
||||
login.setClientSecret("clientSecret");
|
||||
testIssuerConfiguration(login, "okta", 1, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void issuerUriConfigurationTriesOAuthMetadataUriThird() throws Exception {
|
||||
OAuth2ClientProperties.Registration login = new Registration();
|
||||
login.setClientId("clientId");
|
||||
login.setClientSecret("clientSecret");
|
||||
testIssuerConfiguration(login, "okta", 2, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void oidcProviderConfigurationWithCustomConfigurationOverridesProviderDefaults() throws Exception {
|
||||
this.server = new MockWebServer();
|
||||
this.server.start();
|
||||
String issuer = this.server.url("").toString();
|
||||
setupMockResponse(issuer);
|
||||
OAuth2ClientProperties.Registration registration = createRegistration("okta-oidc");
|
||||
Provider provider = createProvider();
|
||||
provider.setIssuerUri(issuer);
|
||||
OAuth2ClientProperties properties = new OAuth2ClientProperties();
|
||||
properties.getProvider().put("okta-oidc", provider);
|
||||
properties.getRegistration().put("okta", registration);
|
||||
Map<String, ClientRegistration> registrations = new OAuth2ClientPropertiesMapper(properties)
|
||||
.asClientRegistrations();
|
||||
ClientRegistration adapted = registrations.get("okta");
|
||||
ProviderDetails providerDetails = adapted.getProviderDetails();
|
||||
assertThat(adapted.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_POST);
|
||||
assertThat(adapted.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
assertThat(adapted.getRegistrationId()).isEqualTo("okta");
|
||||
assertThat(adapted.getClientName()).isEqualTo(issuer);
|
||||
assertThat(adapted.getScopes()).containsOnly("user");
|
||||
assertThat(adapted.getRedirectUri()).isEqualTo("https://example.com/redirect");
|
||||
assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://example.com/auth");
|
||||
assertThat(providerDetails.getTokenUri()).isEqualTo("https://example.com/token");
|
||||
assertThat(providerDetails.getJwkSetUri()).isEqualTo("https://example.com/jwk");
|
||||
UserInfoEndpoint userInfoEndpoint = providerDetails.getUserInfoEndpoint();
|
||||
assertThat(userInfoEndpoint.getUri()).isEqualTo("https://example.com/info");
|
||||
assertThat(userInfoEndpoint.getUserNameAttributeName()).isEqualTo("sub");
|
||||
}
|
||||
|
||||
private Provider createProvider() {
|
||||
Provider provider = new Provider();
|
||||
provider.setAuthorizationUri("https://example.com/auth");
|
||||
provider.setTokenUri("https://example.com/token");
|
||||
provider.setUserInfoUri("https://example.com/info");
|
||||
provider.setUserNameAttribute("sub");
|
||||
provider.setJwkSetUri("https://example.com/jwk");
|
||||
return provider;
|
||||
}
|
||||
|
||||
private OAuth2ClientProperties.Registration createRegistration(String provider) {
|
||||
OAuth2ClientProperties.Registration registration = new OAuth2ClientProperties.Registration();
|
||||
registration.setProvider(provider);
|
||||
registration.setClientId("clientId");
|
||||
registration.setClientSecret("clientSecret");
|
||||
registration.setClientAuthenticationMethod("client_secret_post");
|
||||
registration.setRedirectUri("https://example.com/redirect");
|
||||
registration.setScope(Collections.singleton("user"));
|
||||
registration.setAuthorizationGrantType("authorization_code");
|
||||
return registration;
|
||||
}
|
||||
|
||||
private void testIssuerConfiguration(OAuth2ClientProperties.Registration registration, String providerId,
|
||||
int errorResponseCount, int numberOfRequests) throws Exception {
|
||||
this.server = new MockWebServer();
|
||||
this.server.start();
|
||||
String issuer = this.server.url("").toString();
|
||||
setupMockResponsesWithErrors(issuer, errorResponseCount);
|
||||
OAuth2ClientProperties properties = new OAuth2ClientProperties();
|
||||
Provider provider = new Provider();
|
||||
provider.setIssuerUri(issuer);
|
||||
properties.getProvider().put(providerId, provider);
|
||||
properties.getRegistration().put("okta", registration);
|
||||
Map<String, ClientRegistration> registrations = new OAuth2ClientPropertiesMapper(properties)
|
||||
.asClientRegistrations();
|
||||
ClientRegistration adapted = registrations.get("okta");
|
||||
ProviderDetails providerDetails = adapted.getProviderDetails();
|
||||
assertThat(adapted.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
|
||||
assertThat(adapted.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
assertThat(adapted.getRegistrationId()).isEqualTo("okta");
|
||||
assertThat(adapted.getClientName()).isEqualTo(issuer);
|
||||
assertThat(adapted.getScopes()).isNull();
|
||||
assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://example.com/o/oauth2/v2/auth");
|
||||
assertThat(providerDetails.getTokenUri()).isEqualTo("https://example.com/oauth2/v4/token");
|
||||
assertThat(providerDetails.getJwkSetUri()).isEqualTo("https://example.com/oauth2/v3/certs");
|
||||
UserInfoEndpoint userInfoEndpoint = providerDetails.getUserInfoEndpoint();
|
||||
assertThat(userInfoEndpoint.getUri()).isEqualTo("https://example.com/oauth2/v3/userinfo");
|
||||
assertThat(userInfoEndpoint.getAuthenticationMethod())
|
||||
.isEqualTo(org.springframework.security.oauth2.core.AuthenticationMethod.HEADER);
|
||||
assertThat(this.server.getRequestCount()).isEqualTo(numberOfRequests);
|
||||
}
|
||||
|
||||
private void setupMockResponse(String issuer) throws JsonProcessingException {
|
||||
MockResponse mockResponse = new MockResponse().setResponseCode(HttpStatus.OK.value())
|
||||
.setBody(new ObjectMapper().writeValueAsString(getResponse(issuer)))
|
||||
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
this.server.enqueue(mockResponse);
|
||||
}
|
||||
|
||||
private void setupMockResponsesWithErrors(String issuer, int errorResponseCount) throws JsonProcessingException {
|
||||
for (int i = 0; i < errorResponseCount; i++) {
|
||||
MockResponse emptyResponse = new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.value());
|
||||
this.server.enqueue(emptyResponse);
|
||||
}
|
||||
setupMockResponse(issuer);
|
||||
}
|
||||
|
||||
private Map<String, Object> getResponse(String issuer) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("authorization_endpoint", "https://example.com/o/oauth2/v2/auth");
|
||||
response.put("claims_supported", Collections.emptyList());
|
||||
response.put("code_challenge_methods_supported", Collections.emptyList());
|
||||
response.put("id_token_signing_alg_values_supported", Collections.emptyList());
|
||||
response.put("issuer", issuer);
|
||||
response.put("jwks_uri", "https://example.com/oauth2/v3/certs");
|
||||
response.put("response_types_supported", Collections.emptyList());
|
||||
response.put("revocation_endpoint", "https://example.com/o/oauth2/revoke");
|
||||
response.put("scopes_supported", Collections.singletonList("openid"));
|
||||
response.put("subject_types_supported", Collections.singletonList("public"));
|
||||
response.put("grant_types_supported", Collections.singletonList("authorization_code"));
|
||||
response.put("token_endpoint", "https://example.com/oauth2/v4/token");
|
||||
response.put("token_endpoint_auth_methods_supported", Collections.singletonList("client_secret_basic"));
|
||||
response.put("userinfo_endpoint", "https://example.com/oauth2/v3/userinfo");
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2ClientProperties}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Artsiom Yudovin
|
||||
*/
|
||||
class OAuth2ClientPropertiesTests {
|
||||
|
||||
private final OAuth2ClientProperties properties = new OAuth2ClientProperties();
|
||||
|
||||
@Test
|
||||
void clientIdAbsentThrowsException() {
|
||||
OAuth2ClientProperties.Registration registration = new OAuth2ClientProperties.Registration();
|
||||
registration.setClientSecret("secret");
|
||||
registration.setProvider("google");
|
||||
this.properties.getRegistration().put("foo", registration);
|
||||
assertThatIllegalStateException().isThrownBy(this.properties::validate)
|
||||
.withMessageContaining("Client id of registration 'foo' must not be empty.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void clientSecretAbsentShouldNotThrowException() {
|
||||
OAuth2ClientProperties.Registration registration = new OAuth2ClientProperties.Registration();
|
||||
registration.setClientId("foo");
|
||||
registration.setProvider("google");
|
||||
this.properties.getRegistration().put("foo", registration);
|
||||
this.properties.validate();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure.reactive;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.security.autoconfigure.reactive.ReactiveSecurityAutoConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link ReactiveOAuth2ClientAutoConfiguration}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class ReactiveOAuth2ClientAutoConfigurationTests {
|
||||
|
||||
private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration";
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(ReactiveOAuth2ClientAutoConfiguration.class,
|
||||
ReactiveSecurityAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void autoConfigurationShouldBackOffForServletEnvironments() {
|
||||
new WebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(ReactiveOAuth2ClientAutoConfiguration.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(ReactiveOAuth2ClientAutoConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void beansShouldNotBeCreatedWhenPropertiesAbsent() {
|
||||
this.contextRunner
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(ReactiveClientRegistrationRepository.class)
|
||||
.doesNotHaveBean(ReactiveOAuth2AuthorizedClientService.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void beansAreCreatedWhenPropertiesPresent() {
|
||||
this.contextRunner
|
||||
.withPropertyValues(REGISTRATION_PREFIX + ".foo.client-id=abcd",
|
||||
REGISTRATION_PREFIX + ".foo.client-secret=secret", REGISTRATION_PREFIX + ".foo.provider=github")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ReactiveClientRegistrationRepository.class);
|
||||
assertThat(context).hasSingleBean(ReactiveOAuth2AuthorizedClientService.class);
|
||||
ReactiveClientRegistrationRepository repository = context
|
||||
.getBean(ReactiveClientRegistrationRepository.class);
|
||||
ClientRegistration registration = repository.findByRegistrationId("foo").block(Duration.ofSeconds(30));
|
||||
assertThat(registration).isNotNull();
|
||||
assertThat(registration.getClientSecret()).isEqualTo("secret");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void clientServiceBeanIsConditionalOnMissingBean() {
|
||||
this.contextRunner
|
||||
.withBean("testAuthorizedClientService", ReactiveOAuth2AuthorizedClientService.class,
|
||||
() -> mock(ReactiveOAuth2AuthorizedClientService.class))
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ReactiveOAuth2AuthorizedClientService.class);
|
||||
assertThat(context).hasBean("testAuthorizedClientService");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void clientServiceBeanIsCreatedWithUserDefinedClientRegistrationRepository() {
|
||||
this.contextRunner
|
||||
.withBean(InMemoryReactiveClientRegistrationRepository.class,
|
||||
() -> new InMemoryReactiveClientRegistrationRepository(getClientRegistration("test", "test")))
|
||||
.run((context) -> assertThat(context).hasSingleBean(ReactiveOAuth2AuthorizedClientService.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoConfigurationConditionalOnClassFlux() {
|
||||
assertWhenClassNotPresent(Flux.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoConfigurationConditionalOnClassClientRegistration() {
|
||||
assertWhenClassNotPresent(ClientRegistration.class);
|
||||
}
|
||||
|
||||
private void assertWhenClassNotPresent(Class<?> classToFilter) {
|
||||
FilteredClassLoader classLoader = new FilteredClassLoader(classToFilter);
|
||||
this.contextRunner.withClassLoader(classLoader)
|
||||
.withPropertyValues(REGISTRATION_PREFIX + ".foo.client-id=abcd",
|
||||
REGISTRATION_PREFIX + ".foo.client-secret=secret", REGISTRATION_PREFIX + ".foo.provider=github")
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(ReactiveOAuth2ClientAutoConfiguration.class));
|
||||
}
|
||||
|
||||
private ClientRegistration getClientRegistration(String id, String userInfoUri) {
|
||||
ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(id);
|
||||
builder.clientName("foo")
|
||||
.clientId("foo")
|
||||
.clientAuthenticationMethod(
|
||||
org.springframework.security.oauth2.core.ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.scope("read")
|
||||
.clientSecret("secret")
|
||||
.redirectUri("https://redirect-uri.com")
|
||||
.authorizationUri("https://authorization-uri.com")
|
||||
.tokenUri("https://token-uri.com")
|
||||
.userInfoUri(userInfoUri)
|
||||
.userNameAttributeName("login");
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure.reactive;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.security.autoconfigure.reactive.ReactiveSecurityAutoConfiguration;
|
||||
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.config.BeanIds;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationCodeGrantWebFilter;
|
||||
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ReactiveOAuth2ClientWebSecurityAutoConfiguration}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class ReactiveOAuth2ClientWebSecurityAutoConfigurationTests {
|
||||
|
||||
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(ReactiveOAuth2ClientWebSecurityAutoConfiguration.class,
|
||||
ReactiveSecurityAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void autoConfigurationShouldBackOffForServletEnvironments() {
|
||||
new WebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(ReactiveOAuth2ClientWebSecurityAutoConfiguration.class))
|
||||
.run((context) -> assertThat(context)
|
||||
.doesNotHaveBean(ReactiveOAuth2ClientWebSecurityAutoConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoConfigurationIsConditionalOnAuthorizedClientService() {
|
||||
this.contextRunner.run((context) -> assertThat(context)
|
||||
.doesNotHaveBean(ReactiveOAuth2ClientWebSecurityAutoConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void configurationRegistersAuthorizedClientRepositoryBean() {
|
||||
this.contextRunner.withUserConfiguration(ReactiveOAuth2AuthorizedClientServiceConfiguration.class)
|
||||
.run((context) -> assertThat(context)
|
||||
.hasSingleBean(AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizedClientRepositoryBeanIsConditionalOnMissingBean() {
|
||||
this.contextRunner.withUserConfiguration(ReactiveOAuth2AuthorizedClientRepositoryConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ServerOAuth2AuthorizedClientRepository.class);
|
||||
assertThat(context).hasBean("testAuthorizedClientRepository");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void configurationRegistersSecurityWebFilterChainBean() { // gh-17949
|
||||
this.contextRunner
|
||||
.withUserConfiguration(ReactiveOAuth2AuthorizedClientServiceConfiguration.class,
|
||||
ServerHttpSecurityConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(hasFilter(context, OAuth2LoginAuthenticationWebFilter.class)).isTrue();
|
||||
assertThat(hasFilter(context, OAuth2AuthorizationCodeGrantWebFilter.class)).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void securityWebFilterChainBeanConditionalOnWebApplication() {
|
||||
new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(ReactiveOAuth2ClientWebSecurityAutoConfiguration.class,
|
||||
ReactiveSecurityAutoConfiguration.class))
|
||||
.withUserConfiguration(ReactiveOAuth2AuthorizedClientRepositoryConfiguration.class)
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(SecurityWebFilterChain.class));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean hasFilter(AssertableReactiveWebApplicationContext context, Class<? extends WebFilter> filter) {
|
||||
SecurityWebFilterChain filterChain = (SecurityWebFilterChain) context
|
||||
.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
|
||||
List<WebFilter> filters = (List<WebFilter>) ReflectionTestUtils.getField(filterChain, "filters");
|
||||
return filters.stream().anyMatch(filter::isInstance);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(ReactiveClientRepositoryConfiguration.class)
|
||||
static class ReactiveOAuth2AuthorizedClientServiceConfiguration {
|
||||
|
||||
@Bean
|
||||
InMemoryReactiveOAuth2AuthorizedClientService testAuthorizedClientService(
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository) {
|
||||
return new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(ReactiveOAuth2AuthorizedClientServiceConfiguration.class)
|
||||
static class ReactiveOAuth2AuthorizedClientRepositoryConfiguration {
|
||||
|
||||
@Bean
|
||||
ServerOAuth2AuthorizedClientRepository testAuthorizedClientRepository(
|
||||
ReactiveOAuth2AuthorizedClientService authorizedClientService) {
|
||||
return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ReactiveClientRepositoryConfiguration {
|
||||
|
||||
@Bean
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository() {
|
||||
List<ClientRegistration> registrations = new ArrayList<>();
|
||||
registrations.add(getClientRegistration("first", "https://user-info-uri.com"));
|
||||
registrations.add(getClientRegistration("second", "https://other-user-info"));
|
||||
return new InMemoryReactiveClientRegistrationRepository(registrations);
|
||||
}
|
||||
|
||||
private ClientRegistration getClientRegistration(String id, String userInfoUri) {
|
||||
ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(id);
|
||||
builder.clientName("foo")
|
||||
.clientId("foo")
|
||||
.clientAuthenticationMethod(
|
||||
org.springframework.security.oauth2.core.ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.scope("read")
|
||||
.clientSecret("secret")
|
||||
.redirectUri("https://redirect-uri.com")
|
||||
.authorizationUri("https://authorization-uri.com")
|
||||
.tokenUri("https://token-uri.com")
|
||||
.userInfoUri(userInfoUri)
|
||||
.userNameAttributeName("login");
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ServerHttpSecurityConfiguration {
|
||||
|
||||
@Bean
|
||||
ServerHttpSecurity http() {
|
||||
TestServerHttpSecurity httpSecurity = new TestServerHttpSecurity();
|
||||
return httpSecurity;
|
||||
}
|
||||
|
||||
static class TestServerHttpSecurity extends ServerHttpSecurity implements ApplicationContextAware {
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||
super.setApplicationContext(applicationContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.security.oauth2.client.autoconfigure.servlet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;
|
||||
import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.config.BeanIds;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.filter.CompositeFilter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2ClientWebSecurityAutoConfiguration}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class OAuth2ClientWebSecurityAutoConfigurationTests {
|
||||
|
||||
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(OAuth2ClientWebSecurityAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void autoConfigurationIsConditionalOnAuthorizedClientService() {
|
||||
this.contextRunner
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(OAuth2ClientWebSecurityAutoConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void configurationRegistersAuthorizedClientRepositoryBean() {
|
||||
this.contextRunner.withUserConfiguration(OAuth2AuthorizedClientServiceConfiguration.class)
|
||||
.run((context) -> assertThat(context).hasSingleBean(OAuth2AuthorizedClientRepository.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizedClientRepositoryBeanIsConditionalOnMissingBean() {
|
||||
this.contextRunner.withUserConfiguration(OAuth2AuthorizedClientRepositoryConfiguration.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(OAuth2AuthorizedClientRepository.class);
|
||||
assertThat(context).hasBean("testAuthorizedClientRepository");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void securityConfigurerConfiguresOAuth2Login() {
|
||||
this.contextRunner.withUserConfiguration(OAuth2AuthorizedClientServiceConfiguration.class).run((context) -> {
|
||||
ClientRegistrationRepository expected = context.getBean(ClientRegistrationRepository.class);
|
||||
ClientRegistrationRepository actual = (ClientRegistrationRepository) ReflectionTestUtils.getField(
|
||||
getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class).get(0),
|
||||
"clientRegistrationRepository");
|
||||
assertThat(isEqual(expected.findByRegistrationId("first"), actual.findByRegistrationId("first"))).isTrue();
|
||||
assertThat(isEqual(expected.findByRegistrationId("second"), actual.findByRegistrationId("second")))
|
||||
.isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void securityConfigurerConfiguresAuthorizationCode() {
|
||||
this.contextRunner.withUserConfiguration(OAuth2AuthorizedClientServiceConfiguration.class).run((context) -> {
|
||||
ClientRegistrationRepository expected = context.getBean(ClientRegistrationRepository.class);
|
||||
ClientRegistrationRepository actual = (ClientRegistrationRepository) ReflectionTestUtils.getField(
|
||||
getSecurityFilters(context, OAuth2AuthorizationCodeGrantFilter.class).get(0),
|
||||
"clientRegistrationRepository");
|
||||
assertThat(isEqual(expected.findByRegistrationId("first"), actual.findByRegistrationId("first"))).isTrue();
|
||||
assertThat(isEqual(expected.findByRegistrationId("second"), actual.findByRegistrationId("second")))
|
||||
.isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void securityConfigurerBacksOffWhenClientRegistrationBeanAbsent() {
|
||||
this.contextRunner.withUserConfiguration(TestConfig.class).run((context) -> {
|
||||
assertThat(getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty();
|
||||
assertThat(getSecurityFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void securityFilterChainConfigBacksOffWhenOtherSecurityFilterChainBeanPresent() {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(WebMvcAutoConfiguration.class))
|
||||
.withUserConfiguration(TestSecurityFilterChainConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty();
|
||||
assertThat(getSecurityFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty();
|
||||
assertThat(context).getBean(OAuth2AuthorizedClientService.class).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void securityFilterChainConfigConditionalOnSecurityFilterChainClass() {
|
||||
this.contextRunner.withUserConfiguration(ClientRegistrationRepositoryConfiguration.class)
|
||||
.withClassLoader(new FilteredClassLoader(SecurityFilterChain.class))
|
||||
.run((context) -> {
|
||||
assertThat(getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty();
|
||||
assertThat(getSecurityFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
private List<Filter> getSecurityFilters(AssertableWebApplicationContext context, Class<? extends Filter> filter) {
|
||||
return getSecurityFilterChain(context).getFilters().stream().filter(filter::isInstance).toList();
|
||||
}
|
||||
|
||||
private SecurityFilterChain getSecurityFilterChain(AssertableWebApplicationContext context) {
|
||||
Filter springSecurityFilterChain = context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN, Filter.class);
|
||||
FilterChainProxy filterChainProxy = getFilterChainProxy(springSecurityFilterChain);
|
||||
SecurityFilterChain securityFilterChain = filterChainProxy.getFilterChains().get(0);
|
||||
return securityFilterChain;
|
||||
}
|
||||
|
||||
private FilterChainProxy getFilterChainProxy(Filter filter) {
|
||||
if (filter instanceof FilterChainProxy filterChainProxy) {
|
||||
return filterChainProxy;
|
||||
}
|
||||
if (filter instanceof CompositeFilter) {
|
||||
List<?> filters = (List<?>) ReflectionTestUtils.getField(filter, "filters");
|
||||
return (FilterChainProxy) filters.stream()
|
||||
.filter(FilterChainProxy.class::isInstance)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
throw new IllegalStateException("No FilterChainProxy found");
|
||||
}
|
||||
|
||||
private boolean isEqual(ClientRegistration reg1, ClientRegistration reg2) {
|
||||
boolean result = ObjectUtils.nullSafeEquals(reg1.getClientId(), reg2.getClientId());
|
||||
result = result && ObjectUtils.nullSafeEquals(reg1.getClientName(), reg2.getClientName());
|
||||
result = result && ObjectUtils.nullSafeEquals(reg1.getClientSecret(), reg2.getClientSecret());
|
||||
result = result && ObjectUtils.nullSafeEquals(reg1.getScopes(), reg2.getScopes());
|
||||
result = result && ObjectUtils.nullSafeEquals(reg1.getRedirectUri(), reg2.getRedirectUri());
|
||||
result = result && ObjectUtils.nullSafeEquals(reg1.getRegistrationId(), reg2.getRegistrationId());
|
||||
result = result
|
||||
&& ObjectUtils.nullSafeEquals(reg1.getAuthorizationGrantType(), reg2.getAuthorizationGrantType());
|
||||
result = result && ObjectUtils.nullSafeEquals(reg1.getProviderDetails().getAuthorizationUri(),
|
||||
reg2.getProviderDetails().getAuthorizationUri());
|
||||
result = result && ObjectUtils.nullSafeEquals(reg1.getProviderDetails().getUserInfoEndpoint(),
|
||||
reg2.getProviderDetails().getUserInfoEndpoint());
|
||||
result = result && ObjectUtils.nullSafeEquals(reg1.getProviderDetails().getTokenUri(),
|
||||
reg2.getProviderDetails().getTokenUri());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableWebSecurity
|
||||
static class TestConfig {
|
||||
|
||||
@Bean
|
||||
TomcatServletWebServerFactory tomcat() {
|
||||
return new TomcatServletWebServerFactory(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(ClientRegistrationRepositoryConfiguration.class)
|
||||
static class OAuth2AuthorizedClientServiceConfiguration {
|
||||
|
||||
@Bean
|
||||
InMemoryOAuth2AuthorizedClientService authorizedClientService(
|
||||
ClientRegistrationRepository clientRegistrationRepository) {
|
||||
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(OAuth2AuthorizedClientServiceConfiguration.class)
|
||||
static class OAuth2AuthorizedClientRepositoryConfiguration {
|
||||
|
||||
@Bean
|
||||
OAuth2AuthorizedClientRepository testAuthorizedClientRepository(
|
||||
OAuth2AuthorizedClientService authorizedClientService) {
|
||||
return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(TestConfig.class)
|
||||
static class ClientRegistrationRepositoryConfiguration {
|
||||
|
||||
@Bean
|
||||
ClientRegistrationRepository clientRegistrationRepository() {
|
||||
List<ClientRegistration> registrations = new ArrayList<>();
|
||||
registrations.add(getClientRegistration("first", "https://user-info-uri.com"));
|
||||
registrations.add(getClientRegistration("second", "https://other-user-info"));
|
||||
return new InMemoryClientRegistrationRepository(registrations);
|
||||
}
|
||||
|
||||
private ClientRegistration getClientRegistration(String id, String userInfoUri) {
|
||||
ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(id);
|
||||
builder.clientName("foo")
|
||||
.clientId("foo")
|
||||
.clientAuthenticationMethod(
|
||||
org.springframework.security.oauth2.core.ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.scope("read")
|
||||
.clientSecret("secret")
|
||||
.redirectUri("https://redirect-uri.com")
|
||||
.authorizationUri("https://authorization-uri.com")
|
||||
.tokenUri("https://token-uri.com")
|
||||
.userInfoUri(userInfoUri)
|
||||
.userNameAttributeName("login");
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(OAuth2AuthorizedClientServiceConfiguration.class)
|
||||
static class TestSecurityFilterChainConfiguration {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
return http.securityMatcher("/**")
|
||||
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user