Restructure OAuth2 client auto-configuration

Previously, OAuth2 client auto-configuration was managed by a single
class:

- OAuth2ClientAutoConfiguration for servlet apps
- ReactiveOAuth2ClientAutoConfiguration for reactive apps

OAuth2ClientAutoConfiguration being for servlet apps meant that
a blocking OAuth2 client was not availabile in a non-web application.

The auto-configuration classes did two things:

- Auto-configured beans that are specific to server-side web security
  that uses an OAuth2 client
- Auto-configured OAuth2 client beans that may be used client- or
  server-side

Combining these two things into a single auto-configuration class
meant that you could not choose to use one or the other. For example,
you may want to make use of an OAuth2 client in a web application
without also using OAuth2 client-based web security.

This commit restructures the auto-configuration to address these
problems. There are now two auto-configurations for non-reactive apps:

- OAuth2ClientAutoConfiguration
- OAuth2ClientWebSecurityAutoConfiguration

and two auto-configurations for reactive apps:

- ReactiveOAuth2ClientAutoConfiguration
- ReactiveOAuth2ClientWebSecurityAutoConfiguration

This separation allows one to be used without the other. Furthermore,
the conditions have been updated so that, for example, the blocking
OAuth2 client is available in a non-web application.

Closes gh-40997
Closes gh-44906

Co-authored-by: Moritz Halbritter <moritz.halbritter@broadcom.com>
This commit is contained in:
Andy Wilkinson
2025-04-17 12:17:31 +01:00
parent c6e47d063a
commit 231396e6ba
19 changed files with 611 additions and 418 deletions

View File

@@ -28,7 +28,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration;
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration;
@@ -57,7 +57,7 @@ import static org.springframework.security.config.Customizer.withDefaults;
*/
@AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class,
after = { HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class, ReactiveOAuth2ClientAutoConfiguration.class,
WebEndpointAutoConfiguration.class, ReactiveOAuth2ClientWebSecurityAutoConfiguration.class,
ReactiveOAuth2ResourceServerAutoConfiguration.class,
ReactiveUserDetailsServiceAutoConfiguration.class })
@ConditionalOnClass({ EnableWebFluxSecurity.class, WebFilterChainProxy.class })

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* 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.
@@ -26,7 +26,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.ConditionalOnDefaultWebSecurity;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration;
import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
@@ -53,7 +53,7 @@ import static org.springframework.security.config.Customizer.withDefaults;
*/
@AutoConfiguration(before = SecurityAutoConfiguration.class,
after = { HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class, OAuth2ClientAutoConfiguration.class,
WebEndpointAutoConfiguration.class, OAuth2ClientWebSecurityAutoConfiguration.class,
OAuth2ResourceServerAutoConfiguration.class, Saml2RelyingPartyAutoConfiguration.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnDefaultWebSecurity

View File

@@ -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.autoconfigure.security.oauth2.client;
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.autoconfigure.security.oauth2.client.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 {
}
}
}

View File

@@ -14,39 +14,56 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.security.oauth2.client.servlet;
package org.springframework.boot.autoconfigure.security.oauth2.client;
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.autoconfigure.security.oauth2.client.ConditionalOnOAuth2ClientRegistrationProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.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;
/**
* {@link Configuration @Configuration} used to map {@link OAuth2ClientProperties} to
* client registrations.
* Configurations related to auto-configuration of OAuth2 client support.
*
* @author Madhura Bhave
* @author Andy Wilkinson
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(OAuth2ClientProperties.class)
@ConditionalOnOAuth2ClientRegistrationProperties
class OAuth2ClientRegistrationRepositoryConfiguration {
class OAuth2ClientConfigurations {
@Bean
@Configuration(proxyBeanMethods = false)
@ConditionalOnOAuth2ClientRegistrationProperties
@EnableConfigurationProperties(OAuth2ClientProperties.class)
@ConditionalOnMissingBean(ClientRegistrationRepository.class)
InMemoryClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) {
List<ClientRegistration> registrations = new ArrayList<>(
new OAuth2ClientPropertiesMapper(properties).asClientRegistrations().values());
return new InMemoryClientRegistrationRepository(registrations);
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);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* 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.
@@ -23,12 +23,8 @@ 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.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
/**
@@ -38,12 +34,11 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
* @author Madhura Bhave
* @since 2.1.0
*/
@AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class)
@EnableConfigurationProperties(OAuth2ClientProperties.class)
@AutoConfiguration
@Conditional(ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition.class)
@ConditionalOnClass({ Flux.class, EnableWebFluxSecurity.class, ClientRegistration.class })
@ConditionalOnClass({ Flux.class, ClientRegistration.class })
@Import({ ReactiveOAuth2ClientConfigurations.ReactiveClientRegistrationRepositoryConfiguration.class,
ReactiveOAuth2ClientConfigurations.ReactiveOAuth2ClientConfiguration.class })
ReactiveOAuth2ClientConfigurations.ReactiveOAuth2AuthorizedClientServiceConfiguration.class })
public class ReactiveOAuth2ClientAutoConfiguration {
static class NonServletApplicationCondition extends NoneNestedConditions {

View File

@@ -21,23 +21,17 @@ import java.util.List;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.oauth2.client.ConditionalOnOAuth2ClientRegistrationProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.web.server.SecurityWebFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* Reactive OAuth2 Client configurations.
@@ -47,12 +41,14 @@ import static org.springframework.security.config.Customizer.withDefaults;
class ReactiveOAuth2ClientConfigurations {
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(OAuth2ClientProperties.class)
@ConditionalOnOAuth2ClientRegistrationProperties
@ConditionalOnMissingBean(ReactiveClientRegistrationRepository.class)
static class ReactiveClientRegistrationRepositoryConfiguration {
@Bean
InMemoryReactiveClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) {
InMemoryReactiveClientRegistrationRepository reactiveClientRegistrationRepository(
OAuth2ClientProperties properties) {
List<ClientRegistration> registrations = new ArrayList<>(
new OAuth2ClientPropertiesMapper(properties).asClientRegistrations().values());
return new InMemoryReactiveClientRegistrationRepository(registrations);
@@ -62,37 +58,15 @@ class ReactiveOAuth2ClientConfigurations {
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(ReactiveClientRegistrationRepository.class)
static class ReactiveOAuth2ClientConfiguration {
static class ReactiveOAuth2AuthorizedClientServiceConfiguration {
@Bean
@ConditionalOnMissingBean
ReactiveOAuth2AuthorizedClientService authorizedClientService(
ReactiveOAuth2AuthorizedClientService reactiveAuthorizedClientService(
ReactiveClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository);
}
@Bean
@ConditionalOnMissingBean
ServerOAuth2AuthorizedClientRepository authorizedClientRepository(
ReactiveOAuth2AuthorizedClientService authorizedClientService) {
return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
static class SecurityWebFilterChainConfiguration {
@Bean
@ConditionalOnMissingBean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange((exchange) -> exchange.anyExchange().authenticated());
http.oauth2Login(withDefaults());
http.oauth2Client(withDefaults());
return http.build();
}
}
}
}

View File

@@ -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.autoconfigure.security.oauth2.client.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.autoconfigure.security.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 3.5.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();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* 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.
@@ -16,14 +16,7 @@
package org.springframework.boot.autoconfigure.security.oauth2.client.servlet;
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.security.servlet.SecurityAutoConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for OAuth client support.
@@ -31,11 +24,10 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
* @author Madhura Bhave
* @author Phillip Webb
* @since 2.0.0
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
* {@link org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration}
*/
@AutoConfiguration(before = SecurityAutoConfiguration.class)
@ConditionalOnClass({ EnableWebSecurity.class, ClientRegistration.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Import({ OAuth2ClientRegistrationRepositoryConfiguration.class, OAuth2WebSecurityConfiguration.class })
@Deprecated(since = "3.5.0", forRemoval = true)
public class OAuth2ClientAutoConfiguration {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@@ -16,15 +16,19 @@
package org.springframework.boot.autoconfigure.security.oauth2.client.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.autoconfigure.security.ConditionalOnDefaultWebSecurity;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
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.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.web.SecurityFilterChain;
@@ -32,20 +36,18 @@ import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* {@link SecurityFilterChain} to add OAuth client support.
* Auto-configuration for web security that uses an OAuth 2 client.
*
* @author Madhura Bhave
* @author Phillip Webb
* @author Andy Wilkinson
* @since 3.5.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(ClientRegistrationRepository.class)
class OAuth2WebSecurityConfiguration {
@Bean
@ConditionalOnMissingBean
OAuth2AuthorizedClientService authorizedClientService(ClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}
@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

View File

@@ -115,8 +115,10 @@ org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfi
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientWebSecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientWebSecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerAutoConfiguration

View File

@@ -0,0 +1 @@
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration=org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@@ -17,8 +17,6 @@
package org.springframework.boot.autoconfigure.security.oauth2.client.reactive;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
@@ -26,34 +24,16 @@ import reactor.core.publisher.Flux;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
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.annotation.web.reactive.EnableWebFluxSecurity;
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.ClientRegistrationRepository;
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;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ReactiveOAuth2ClientAutoConfiguration}.
@@ -62,12 +42,12 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
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));
private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration";
@Test
void autoConfigurationShouldBackOffForServletEnvironments() {
new WebApplicationContextRunner()
@@ -76,16 +56,20 @@ class ReactiveOAuth2ClientAutoConfigurationTests {
}
@Test
void clientRegistrationRepositoryBeanShouldNotBeCreatedWhenPropertiesAbsent() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ClientRegistrationRepository.class));
void beansShouldNotBeCreatedWhenPropertiesAbsent() {
this.contextRunner
.run((context) -> assertThat(context).doesNotHaveBean(ReactiveClientRegistrationRepository.class)
.doesNotHaveBean(ReactiveOAuth2AuthorizedClientService.class));
}
@Test
void clientRegistrationRepositoryBeanShouldBeCreatedWhenPropertiesPresent() {
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));
@@ -95,24 +79,10 @@ class ReactiveOAuth2ClientAutoConfigurationTests {
}
@Test
void authorizedClientServiceAndRepositoryBeansAreConditionalOnClientRegistrationRepository() {
this.contextRunner.run((context) -> {
assertThat(context).doesNotHaveBean(ReactiveOAuth2AuthorizedClientService.class);
assertThat(context).doesNotHaveBean(ServerOAuth2AuthorizedClientRepository.class);
});
}
@Test
void configurationRegistersAuthorizedClientServiceAndRepositoryBeans() {
this.contextRunner.withUserConfiguration(ReactiveClientRepositoryConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(InMemoryReactiveOAuth2AuthorizedClientService.class);
assertThat(context).hasSingleBean(AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository.class);
});
}
@Test
void authorizedClientServiceBeanIsConditionalOnMissingBean() {
this.contextRunner.withUserConfiguration(ReactiveOAuth2AuthorizedClientRepositoryConfiguration.class)
void clientServiceBeanIsConditionalOnMissingBean() {
this.contextRunner
.withBean("testAuthorizedClientService", ReactiveOAuth2AuthorizedClientService.class,
() -> mock(ReactiveOAuth2AuthorizedClientService.class))
.run((context) -> {
assertThat(context).hasSingleBean(ReactiveOAuth2AuthorizedClientService.class);
assertThat(context).hasBean("testAuthorizedClientService");
@@ -120,43 +90,11 @@ class ReactiveOAuth2ClientAutoConfigurationTests {
}
@Test
void authorizedClientRepositoryBeanIsConditionalOnAuthorizedClientService() {
void clientServiceBeanIsCreatedWithUserDefinedClientRegistrationRepository() {
this.contextRunner
.run((context) -> assertThat(context).doesNotHaveBean(ServerOAuth2AuthorizedClientRepository.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 securityWebFilterChainBeanConditionalOnWebApplication() {
this.contextRunner.withUserConfiguration(ReactiveOAuth2AuthorizedClientRepositoryConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean(SecurityWebFilterChain.class));
}
@Test
void configurationRegistersSecurityWebFilterChainBean() { // gh-17949
new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ReactiveOAuth2ClientAutoConfiguration.class))
.withUserConfiguration(ReactiveOAuth2AuthorizedClientServiceConfiguration.class,
ServerHttpSecurityConfiguration.class)
.run((context) -> {
assertThat(hasFilter(context, OAuth2LoginAuthenticationWebFilter.class)).isTrue();
assertThat(hasFilter(context, OAuth2AuthorizationCodeGrantWebFilter.class)).isTrue();
});
.withBean(InMemoryReactiveClientRegistrationRepository.class,
() -> new InMemoryReactiveClientRegistrationRepository(getClientRegistration("test", "test")))
.run((context) -> assertThat(context).hasSingleBean(ReactiveOAuth2AuthorizedClientService.class));
}
@Test
@@ -164,11 +102,6 @@ class ReactiveOAuth2ClientAutoConfigurationTests {
assertWhenClassNotPresent(Flux.class);
}
@Test
void autoConfigurationConditionalOnClassEnableWebFluxSecurity() {
assertWhenClassNotPresent(EnableWebFluxSecurity.class);
}
@Test
void autoConfigurationConditionalOnClassClientRegistration() {
assertWhenClassNotPresent(ClientRegistration.class);
@@ -182,86 +115,21 @@ class ReactiveOAuth2ClientAutoConfigurationTests {
.run((context) -> assertThat(context).doesNotHaveBean(ReactiveOAuth2ClientAutoConfiguration.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)
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)
@Import(ReactiveClientRepositoryConfiguration.class)
static class ReactiveOAuth2AuthorizedClientServiceConfiguration {
@Bean
ReactiveOAuth2AuthorizedClientService 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 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);
}
}
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();
}
}

View File

@@ -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.autoconfigure.security.oauth2.client.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.autoconfigure.security.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);
}
}
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.autoconfigure.security.oauth2.client.servlet;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration;
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();
}
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.security.oauth2.client.servlet;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OAuth2ClientRegistrationRepositoryConfiguration}.
*
* @author Madhura Bhave
*/
class OAuth2ClientRegistrationRepositoryConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration";
@Test
void clientRegistrationRepositoryBeanShouldNotBeCreatedWhenPropertiesAbsent() {
this.contextRunner.withUserConfiguration(OAuth2ClientRegistrationRepositoryConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean(ClientRegistrationRepository.class));
}
@Test
void clientRegistrationRepositoryBeanShouldBeCreatedWhenPropertiesPresent() {
this.contextRunner.withUserConfiguration(OAuth2ClientRegistrationRepositoryConfiguration.class)
.withPropertyValues(REGISTRATION_PREFIX + ".foo.client-id=abcd",
REGISTRATION_PREFIX + ".foo.client-secret=secret", REGISTRATION_PREFIX + ".foo.provider=github")
.run((context) -> {
ClientRegistrationRepository repository = context.getBean(ClientRegistrationRepository.class);
ClientRegistration registration = repository.findByRegistrationId("foo");
assertThat(registration).isNotNull();
assertThat(registration.getClientSecret()).isEqualTo("secret");
});
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@@ -53,77 +53,74 @@ import org.springframework.web.filter.CompositeFilter;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OAuth2WebSecurityConfiguration}.
* Tests for {@link OAuth2ClientWebSecurityAutoConfiguration}.
*
* @author Madhura Bhave
* @author Andy Wilkinson
*/
class OAuth2WebSecurityConfigurationTests {
class OAuth2ClientWebSecurityAutoConfigurationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner();
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OAuth2ClientWebSecurityAutoConfiguration.class));
@Test
void securityConfigurerConfiguresOAuth2Login() {
void autoConfigurationIsConditionalOnAuthorizedClientService() {
this.contextRunner
.withUserConfiguration(ClientRegistrationRepositoryConfiguration.class,
OAuth2WebSecurityConfiguration.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(ClientRegistrationRepositoryConfiguration.class,
OAuth2WebSecurityConfiguration.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, OAuth2WebSecurityConfiguration.class)
.run((context) -> {
assertThat(getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty();
assertThat(getSecurityFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty();
});
}
@Test
void configurationRegistersAuthorizedClientServiceBean() {
this.contextRunner
.withUserConfiguration(ClientRegistrationRepositoryConfiguration.class,
OAuth2WebSecurityConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(OAuth2AuthorizedClientService.class));
.run((context) -> assertThat(context).doesNotHaveBean(OAuth2ClientWebSecurityAutoConfiguration.class));
}
@Test
void configurationRegistersAuthorizedClientRepositoryBean() {
this.contextRunner
.withUserConfiguration(ClientRegistrationRepositoryConfiguration.class,
OAuth2WebSecurityConfiguration.class)
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, OAuth2WebSecurityConfiguration.class)
.withUserConfiguration(TestSecurityFilterChainConfiguration.class)
.run((context) -> {
assertThat(getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty();
assertThat(getSecurityFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty();
@@ -133,9 +130,7 @@ class OAuth2WebSecurityConfigurationTests {
@Test
void securityFilterChainConfigConditionalOnSecurityFilterChainClass() {
this.contextRunner
.withUserConfiguration(ClientRegistrationRepositoryConfiguration.class,
OAuth2WebSecurityConfiguration.class)
this.contextRunner.withUserConfiguration(ClientRegistrationRepositoryConfiguration.class)
.withClassLoader(new FilteredClassLoader(SecurityFilterChain.class))
.run((context) -> {
assertThat(getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty();
@@ -143,28 +138,6 @@ class OAuth2WebSecurityConfigurationTests {
});
}
@Test
void authorizedClientServiceBeanIsConditionalOnMissingBean() {
this.contextRunner
.withUserConfiguration(OAuth2AuthorizedClientServiceConfiguration.class,
OAuth2WebSecurityConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(OAuth2AuthorizedClientService.class);
assertThat(context).hasBean("testAuthorizedClientService");
});
}
@Test
void authorizedClientRepositoryBeanIsConditionalOnMissingBean() {
this.contextRunner
.withUserConfiguration(OAuth2AuthorizedClientRepositoryConfiguration.class,
OAuth2WebSecurityConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(OAuth2AuthorizedClientRepository.class);
assertThat(context).hasBean("testAuthorizedClientRepository");
});
}
private List<Filter> getSecurityFilters(AssertableWebApplicationContext context, Class<? extends Filter> filter) {
return getSecurityFilterChain(context).getFilters().stream().filter(filter::isInstance).toList();
}
@@ -219,6 +192,30 @@ class OAuth2WebSecurityConfigurationTests {
}
@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 {
@@ -251,7 +248,7 @@ class OAuth2WebSecurityConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
@Import(ClientRegistrationRepositoryConfiguration.class)
@Import(OAuth2AuthorizedClientServiceConfiguration.class)
static class TestSecurityFilterChainConfiguration {
@Bean
@@ -264,28 +261,4 @@ class OAuth2WebSecurityConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
@Import(ClientRegistrationRepositoryConfiguration.class)
static class OAuth2AuthorizedClientServiceConfiguration {
@Bean
OAuth2AuthorizedClientService testAuthorizedClientService(
ClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}
}
@Configuration(proxyBeanMethods = false)
@Import(ClientRegistrationRepositoryConfiguration.class)
static class OAuth2AuthorizedClientRepositoryConfiguration {
@Bean
OAuth2AuthorizedClientRepository testAuthorizedClientRepository(
OAuth2AuthorizedClientService authorizedClientService) {
return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService);
}
}
}

View File

@@ -1,5 +1,6 @@
# AutoConfigureWebClient auto-configuration imports
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientWebSecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration

View File

@@ -2,8 +2,9 @@
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientWebSecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@@ -22,7 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration;
import org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;