From 342a0535d78948381df8ba93e884841c9f0a9668 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Tue, 24 Sep 2019 09:50:45 -0700 Subject: [PATCH] Explicitly configure SecurityWebFilterChain bean for reactive oauth2 client This will ensure that ReactiveManagementWebSecurityAutoConfiguration backs off and that the actuator endpoints are also secured via OAuth2. Fixes gh-17949 --- ...ReactiveOAuth2ClientAutoConfiguration.java | 48 +-------- .../ReactiveOAuth2ClientConfigurations.java | 97 +++++++++++++++++++ ...iveOAuth2ClientAutoConfigurationTests.java | 78 +++++++++++++-- .../pom.xml | 4 + ...eReactiveOAuth2ClientApplicationTests.java | 6 ++ 5 files changed, 180 insertions(+), 53 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientConfigurations.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfiguration.java index 51c4ab39d5..62dc3d7482 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfiguration.java @@ -15,34 +15,21 @@ */ package org.springframework.boot.autoconfigure.security.oauth2.client.reactive; -import java.util.ArrayList; -import java.util.List; - import reactor.core.publisher.Flux; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -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.condition.NoneNestedConditions; -import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; -import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter; import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -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; /** * {@link EnableAutoConfiguration Auto-configuration} for Spring Security's Reactive @@ -56,39 +43,10 @@ import org.springframework.security.oauth2.client.web.server.ServerOAuth2Authori @EnableConfigurationProperties(OAuth2ClientProperties.class) @Conditional(ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition.class) @ConditionalOnClass({ Flux.class, EnableWebFluxSecurity.class, ClientRegistration.class }) +@Import({ ReactiveOAuth2ClientConfigurations.ReactiveClientRegistrationRepositoryConfiguration.class, + ReactiveOAuth2ClientConfigurations.ReactiveOAuth2ClientConfiguration.class }) public class ReactiveOAuth2ClientAutoConfiguration { - private final OAuth2ClientProperties properties; - - public ReactiveOAuth2ClientAutoConfiguration(OAuth2ClientProperties properties) { - this.properties = properties; - } - - @Bean - @Conditional(ClientsConfiguredCondition.class) - @ConditionalOnMissingBean(ReactiveClientRegistrationRepository.class) - public InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() { - List registrations = new ArrayList<>( - OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(this.properties).values()); - return new InMemoryReactiveClientRegistrationRepository(registrations); - } - - @Bean - @ConditionalOnBean(ReactiveClientRegistrationRepository.class) - @ConditionalOnMissingBean - public ReactiveOAuth2AuthorizedClientService authorizedClientService( - ReactiveClientRegistrationRepository clientRegistrationRepository) { - return new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository); - } - - @Bean - @ConditionalOnBean(ReactiveOAuth2AuthorizedClientService.class) - @ConditionalOnMissingBean - public ServerOAuth2AuthorizedClientRepository authorizedClientRepository( - ReactiveOAuth2AuthorizedClientService authorizedClientService) { - return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService); - } - static class NonServletApplicationCondition extends NoneNestedConditions { NonServletApplicationCondition() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientConfigurations.java new file mode 100644 index 0000000000..37cc91377c --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientConfigurations.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012-2019 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.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.ClientsConfiguredCondition; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +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; + +/** + * Reactive OAuth2 Client configurations. + * + * @author Madhura Bhave + */ +class ReactiveOAuth2ClientConfigurations { + + @Configuration + @Conditional(ClientsConfiguredCondition.class) + @ConditionalOnMissingBean(ReactiveClientRegistrationRepository.class) + static class ReactiveClientRegistrationRepositoryConfiguration { + + @Bean + public InMemoryReactiveClientRegistrationRepository clientRegistrationRepository( + OAuth2ClientProperties properties) { + List registrations = new ArrayList<>( + OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values()); + return new InMemoryReactiveClientRegistrationRepository(registrations); + } + + } + + @Configuration + @ConditionalOnBean(ReactiveClientRegistrationRepository.class) + static class ReactiveOAuth2ClientConfiguration { + + @Bean + @ConditionalOnMissingBean + public ReactiveOAuth2AuthorizedClientService authorizedClientService( + ReactiveClientRegistrationRepository clientRegistrationRepository) { + return new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository); + } + + @Bean + @ConditionalOnMissingBean + public ServerOAuth2AuthorizedClientRepository authorizedClientRepository( + ReactiveOAuth2AuthorizedClientService authorizedClientService) { + return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService); + } + + @Configuration + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) + static class SecurityWebFilterChainConfiguration { + + @Bean + @ConditionalOnMissingBean + public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { + http.authorizeExchange().anyExchange().authenticated(); + http.oauth2Login(); + return http.build(); + } + + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfigurationTests.java index 99faf9263e..53964e68f3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfigurationTests.java @@ -18,18 +18,27 @@ package org.springframework.boot.autoconfigure.security.oauth2.client.reactive; import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import org.junit.Test; import reactor.core.publisher.Flux; +import org.springframework.beans.BeansException; 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; @@ -38,7 +47,11 @@ import org.springframework.security.oauth2.client.registration.InMemoryReactiveC 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.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; @@ -49,8 +62,8 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class ReactiveOAuth2ClientAutoConfigurationTests { - private ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ReactiveOAuth2ClientAutoConfiguration.class)); + private ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations + .of(ReactiveOAuth2ClientAutoConfiguration.class, ReactiveSecurityAutoConfiguration.class)); private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration"; @@ -82,15 +95,19 @@ public class ReactiveOAuth2ClientAutoConfigurationTests { } @Test - public void authorizedClientServiceBeanIsConditionalOnClientRegistrationRepository() { - this.contextRunner - .run((context) -> assertThat(context).doesNotHaveBean(ReactiveOAuth2AuthorizedClientService.class)); + public void authorizedClientServiceAndRepositoryBeansAreConditionalOnClientRegistrationRepository() { + this.contextRunner.run((context) -> { + assertThat(context).doesNotHaveBean(ReactiveOAuth2AuthorizedClientService.class); + assertThat(context).doesNotHaveBean(ServerOAuth2AuthorizedClientRepository.class); + }); } @Test - public void configurationRegistersAuthorizedClientServiceBean() { - this.contextRunner.withUserConfiguration(ReactiveClientRepositoryConfiguration.class).run( - (context) -> assertThat(context).hasSingleBean(InMemoryReactiveClientRegistrationRepository.class)); + public void configurationRegistersAuthorizedClientServiceAndRepositoryBeans() { + this.contextRunner.withUserConfiguration(ReactiveClientRepositoryConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(InMemoryReactiveOAuth2AuthorizedClientService.class); + assertThat(context).hasSingleBean(AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository.class); + }); } @Test @@ -124,6 +141,22 @@ public class ReactiveOAuth2ClientAutoConfigurationTests { }); } + @Test + public void securityWebFilterChainBeanConditionalOnWebApplication() { + this.contextRunner.withUserConfiguration(ReactiveOAuth2AuthorizedClientRepositoryConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(SecurityWebFilterChain.class)); + } + + @Test + public void configurationRegistersSecurityWebFilterChainBean() { // gh-17949 + new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ReactiveOAuth2ClientAutoConfiguration.class)) + .withUserConfiguration(ReactiveOAuth2AuthorizedClientServiceConfiguration.class, + ServerHttpSecurityConfiguration.class) + .run((context) -> assertThat(getFilters(context, OAuth2LoginAuthenticationWebFilter.class)) + .isNotNull()); + } + @Test public void autoConfigurationConditionalOnClassFlux() { assertWhenClassNotPresent(Flux.class); @@ -147,6 +180,15 @@ public class ReactiveOAuth2ClientAutoConfigurationTests { .run((context) -> assertThat(context).doesNotHaveBean(ReactiveOAuth2ClientAutoConfiguration.class)); } + @SuppressWarnings("unchecked") + private List getFilters(AssertableReactiveWebApplicationContext context, + Class filter) { + SecurityWebFilterChain filterChain = (SecurityWebFilterChain) context + .getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); + List filters = (List) ReflectionTestUtils.getField(filterChain, "filters"); + return filters.stream().filter(filter::isInstance).collect(Collectors.toList()); + } + @Configuration static class ReactiveClientRepositoryConfiguration { @@ -196,4 +238,24 @@ public class ReactiveOAuth2ClientAutoConfigurationTests { } + @Configuration + 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) throws BeansException { + super.setApplicationContext(applicationContext); + } + + } + + } + } diff --git a/spring-boot-samples/spring-boot-sample-reactive-oauth2-client/pom.xml b/spring-boot-samples/spring-boot-sample-reactive-oauth2-client/pom.xml index b4fc0a173c..eae8296cec 100644 --- a/spring-boot-samples/spring-boot-sample-reactive-oauth2-client/pom.xml +++ b/spring-boot-samples/spring-boot-sample-reactive-oauth2-client/pom.xml @@ -16,6 +16,10 @@ + + org.springframework.boot + spring-boot-starter-actuator + org.springframework.boot spring-boot-starter-oauth2-client diff --git a/spring-boot-samples/spring-boot-sample-reactive-oauth2-client/src/test/java/sample/oauth2/client/SampleReactiveOAuth2ClientApplicationTests.java b/spring-boot-samples/spring-boot-sample-reactive-oauth2-client/src/test/java/sample/oauth2/client/SampleReactiveOAuth2ClientApplicationTests.java index 26c24eb7f5..f1fb5baa8d 100644 --- a/spring-boot-samples/spring-boot-sample-reactive-oauth2-client/src/test/java/sample/oauth2/client/SampleReactiveOAuth2ClientApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-reactive-oauth2-client/src/test/java/sample/oauth2/client/SampleReactiveOAuth2ClientApplicationTests.java @@ -51,4 +51,10 @@ public class SampleReactiveOAuth2ClientApplicationTests { assertThat(bodyString).contains("/oauth2/authorization/github-client-2"); } + @Test + public void actuatorShouldBeSecuredByOAuth() { + this.webTestClient.get().uri("/actuator/health").exchange().expectStatus().isFound().expectHeader() + .valueEquals("Location", "/login"); + } + }