diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java index 5a56f25650..e72f9a5b4b 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; @@ -41,6 +42,7 @@ import org.springframework.web.reactive.result.method.annotation.ArgumentResolve * This {@code Configuration} is imported by {@link EnableWebFluxSecurity} * * @author Rob Winch + * @author Alavudin Kuttikkattil * @since 5.1 */ final class ReactiveOAuth2ClientImportSelector implements ImportSelector { @@ -64,14 +66,12 @@ final class ReactiveOAuth2ClientImportSelector implements ImportSelector { private ReactiveOAuth2AuthorizedClientService authorizedClientService; + private ReactiveOAuth2AuthorizedClientManager authorizedClientManager; + @Override public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) { - if (this.authorizedClientRepository != null && this.clientRegistrationRepository != null) { - ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder - .builder().authorizationCode().refreshToken().clientCredentials().password().build(); - DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager( - this.clientRegistrationRepository, getAuthorizedClientRepository()); - authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); + ReactiveOAuth2AuthorizedClientManager authorizedClientManager = getAuthorizedClientManager(); + if (authorizedClientManager != null) { configurer.addCustomResolver(new OAuth2AuthorizedClientArgumentResolver(authorizedClientManager)); } } @@ -93,6 +93,13 @@ final class ReactiveOAuth2ClientImportSelector implements ImportSelector { } } + @Autowired(required = false) + void setAuthorizedClientManager(List authorizedClientManager) { + if (authorizedClientManager.size() == 1) { + this.authorizedClientManager = authorizedClientManager.get(0); + } + } + private ServerOAuth2AuthorizedClientRepository getAuthorizedClientRepository() { if (this.authorizedClientRepository != null) { return this.authorizedClientRepository; @@ -103,6 +110,23 @@ final class ReactiveOAuth2ClientImportSelector implements ImportSelector { return null; } + private ReactiveOAuth2AuthorizedClientManager getAuthorizedClientManager() { + if (this.authorizedClientManager != null) { + return this.authorizedClientManager; + } + ReactiveOAuth2AuthorizedClientManager authorizedClientManager = null; + if (this.authorizedClientRepository != null && this.clientRegistrationRepository != null) { + ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder + .builder().authorizationCode().refreshToken().clientCredentials().password().build(); + DefaultReactiveOAuth2AuthorizedClientManager defaultReactiveOAuth2AuthorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager( + this.clientRegistrationRepository, getAuthorizedClientRepository()); + defaultReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); + authorizedClientManager = defaultReactiveOAuth2AuthorizedClientManager; + } + + return authorizedClientManager; + } + } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelectorTest.java b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelectorTest.java new file mode 100644 index 0000000000..1ba668d331 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelectorTest.java @@ -0,0 +1,169 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.annotation.web.reactive; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import reactor.core.publisher.Mono; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.test.SpringTestContext; +import org.springframework.security.config.test.SpringTestContextExtension; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.TestClientRegistrations; +import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; +import org.springframework.security.web.server.SecurityWebFilterChain; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.reactive.config.EnableWebFlux; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +/** + * Tests for {@link ReactiveOAuth2ClientImportSelector}. + * + * @author Alavudin Kuttikkattil + */ +@ExtendWith(SpringTestContextExtension.class) +public class ReactiveOAuth2ClientImportSelectorTest { + + public final SpringTestContext spring = new SpringTestContext(this); + + WebTestClient client; + + @Autowired + public void setApplicationContext(ApplicationContext context) { + // @formatter:off + this.client = WebTestClient + .bindToApplicationContext(context) + .build(); + // @formatter:on + } + + @Test + public void requestWhenAuthorizedClientManagerConfiguredThenUsed() { + String clientRegistrationId = "client"; + String principalName = "user"; + ReactiveClientRegistrationRepository clientRegistrationRepository = mock( + ReactiveClientRegistrationRepository.class); + ServerOAuth2AuthorizedClientRepository authorizedClientRepository = mock( + ServerOAuth2AuthorizedClientRepository.class); + ReactiveOAuth2AuthorizedClientManager authorizedClientManager = mock( + ReactiveOAuth2AuthorizedClientManager.class); + ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials() + .registrationId(clientRegistrationId).build(); + OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principalName, + TestOAuth2AccessTokens.noScopes()); + given(authorizedClientManager.authorize(any())).willReturn(Mono.just(authorizedClient)); + OAuth2AuthorizedClientManagerRegisteredConfig.CLIENT_REGISTRATION_REPOSITORY = clientRegistrationRepository; + OAuth2AuthorizedClientManagerRegisteredConfig.AUTHORIZED_CLIENT_REPOSITORY = authorizedClientRepository; + OAuth2AuthorizedClientManagerRegisteredConfig.AUTHORIZED_CLIENT_MANAGER = authorizedClientManager; + this.spring.register(OAuth2AuthorizedClientManagerRegisteredConfig.class).autowire(); + // @formatter:off + this.client + .get() + .uri("http://localhost/authorized-client") + .headers((headers) -> headers.setBasicAuth("user", "password")).exchange().expectStatus().isOk() + .expectBody(String.class).isEqualTo("resolved"); + // @formatter:on + verify(authorizedClientManager).authorize(any()); + verifyNoInteractions(clientRegistrationRepository); + verifyNoInteractions(authorizedClientRepository); + } + + @Test + public void requestWhenAuthorizedClientManagerNotConfigureThenUseDefaultAuthorizedClientManager() { + String clientRegistrationId = "client"; + String principalName = "user"; + ReactiveClientRegistrationRepository clientRegistrationRepository = mock( + ReactiveClientRegistrationRepository.class); + ServerOAuth2AuthorizedClientRepository authorizedClientRepository = mock( + ServerOAuth2AuthorizedClientRepository.class); + ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials() + .registrationId(clientRegistrationId).build(); + OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principalName, + TestOAuth2AccessTokens.noScopes()); + OAuth2AuthorizedClientManagerRegisteredConfig.CLIENT_REGISTRATION_REPOSITORY = clientRegistrationRepository; + OAuth2AuthorizedClientManagerRegisteredConfig.AUTHORIZED_CLIENT_REPOSITORY = authorizedClientRepository; + OAuth2AuthorizedClientManagerRegisteredConfig.AUTHORIZED_CLIENT_MANAGER = null; + given(authorizedClientRepository.loadAuthorizedClient(any(), any(), any())) + .willReturn(Mono.just(authorizedClient)); + this.spring.register(OAuth2AuthorizedClientManagerRegisteredConfig.class).autowire(); + // @formatter:off + this.client + .get() + .uri("http://localhost/authorized-client") + .headers((headers) -> headers.setBasicAuth("user", "password")).exchange().expectStatus().isOk() + .expectBody(String.class).isEqualTo("resolved"); + // @formatter:on + } + + @EnableWebFlux + @EnableWebFluxSecurity + static class OAuth2AuthorizedClientManagerRegisteredConfig { + + static ReactiveClientRegistrationRepository CLIENT_REGISTRATION_REPOSITORY; + static ServerOAuth2AuthorizedClientRepository AUTHORIZED_CLIENT_REPOSITORY; + static ReactiveOAuth2AuthorizedClientManager AUTHORIZED_CLIENT_MANAGER; + + @Bean + SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { + return http.build(); + } + + @Bean + ReactiveClientRegistrationRepository clientRegistrationRepository() { + return CLIENT_REGISTRATION_REPOSITORY; + } + + @Bean + ServerOAuth2AuthorizedClientRepository authorizedClientRepository() { + return AUTHORIZED_CLIENT_REPOSITORY; + } + + @Bean + ReactiveOAuth2AuthorizedClientManager authorizedClientManager() { + return AUTHORIZED_CLIENT_MANAGER; + } + + @RestController + class Controller { + + @GetMapping("/authorized-client") + String authorizedClient( + @RegisteredOAuth2AuthorizedClient("client1") OAuth2AuthorizedClient authorizedClient) { + return (authorizedClient != null) ? "resolved" : "not-resolved"; + } + + } + + } + +}