Skip to content

Commit 342a053

Browse files
committed
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
1 parent c613418 commit 342a053

File tree

5 files changed

+180
-53
lines changed

5 files changed

+180
-53
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfiguration.java

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,21 @@
1515
*/
1616
package org.springframework.boot.autoconfigure.security.oauth2.client.reactive;
1717

18-
import java.util.ArrayList;
19-
import java.util.List;
20-
2118
import reactor.core.publisher.Flux;
2219

2320
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
2421
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
25-
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2622
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
27-
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2823
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
2924
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
30-
import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition;
3125
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
32-
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter;
3326
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
3427
import org.springframework.boot.context.properties.EnableConfigurationProperties;
35-
import org.springframework.context.annotation.Bean;
3628
import org.springframework.context.annotation.Conditional;
3729
import org.springframework.context.annotation.Configuration;
30+
import org.springframework.context.annotation.Import;
3831
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
39-
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
40-
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
4132
import org.springframework.security.oauth2.client.registration.ClientRegistration;
42-
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
43-
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
44-
import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
45-
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
4633

4734
/**
4835
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security's Reactive
@@ -56,39 +43,10 @@
5643
@EnableConfigurationProperties(OAuth2ClientProperties.class)
5744
@Conditional(ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition.class)
5845
@ConditionalOnClass({ Flux.class, EnableWebFluxSecurity.class, ClientRegistration.class })
46+
@Import({ ReactiveOAuth2ClientConfigurations.ReactiveClientRegistrationRepositoryConfiguration.class,
47+
ReactiveOAuth2ClientConfigurations.ReactiveOAuth2ClientConfiguration.class })
5948
public class ReactiveOAuth2ClientAutoConfiguration {
6049

61-
private final OAuth2ClientProperties properties;
62-
63-
public ReactiveOAuth2ClientAutoConfiguration(OAuth2ClientProperties properties) {
64-
this.properties = properties;
65-
}
66-
67-
@Bean
68-
@Conditional(ClientsConfiguredCondition.class)
69-
@ConditionalOnMissingBean(ReactiveClientRegistrationRepository.class)
70-
public InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() {
71-
List<ClientRegistration> registrations = new ArrayList<>(
72-
OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(this.properties).values());
73-
return new InMemoryReactiveClientRegistrationRepository(registrations);
74-
}
75-
76-
@Bean
77-
@ConditionalOnBean(ReactiveClientRegistrationRepository.class)
78-
@ConditionalOnMissingBean
79-
public ReactiveOAuth2AuthorizedClientService authorizedClientService(
80-
ReactiveClientRegistrationRepository clientRegistrationRepository) {
81-
return new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository);
82-
}
83-
84-
@Bean
85-
@ConditionalOnBean(ReactiveOAuth2AuthorizedClientService.class)
86-
@ConditionalOnMissingBean
87-
public ServerOAuth2AuthorizedClientRepository authorizedClientRepository(
88-
ReactiveOAuth2AuthorizedClientService authorizedClientService) {
89-
return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService);
90-
}
91-
9250
static class NonServletApplicationCondition extends NoneNestedConditions {
9351

9452
NonServletApplicationCondition() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.security.oauth2.client.reactive;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
24+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
25+
import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition;
26+
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
27+
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.Conditional;
30+
import org.springframework.context.annotation.Configuration;
31+
import org.springframework.security.config.web.server.ServerHttpSecurity;
32+
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
33+
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
34+
import org.springframework.security.oauth2.client.registration.ClientRegistration;
35+
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
36+
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
37+
import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
38+
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
39+
import org.springframework.security.web.server.SecurityWebFilterChain;
40+
41+
/**
42+
* Reactive OAuth2 Client configurations.
43+
*
44+
* @author Madhura Bhave
45+
*/
46+
class ReactiveOAuth2ClientConfigurations {
47+
48+
@Configuration
49+
@Conditional(ClientsConfiguredCondition.class)
50+
@ConditionalOnMissingBean(ReactiveClientRegistrationRepository.class)
51+
static class ReactiveClientRegistrationRepositoryConfiguration {
52+
53+
@Bean
54+
public InMemoryReactiveClientRegistrationRepository clientRegistrationRepository(
55+
OAuth2ClientProperties properties) {
56+
List<ClientRegistration> registrations = new ArrayList<>(
57+
OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values());
58+
return new InMemoryReactiveClientRegistrationRepository(registrations);
59+
}
60+
61+
}
62+
63+
@Configuration
64+
@ConditionalOnBean(ReactiveClientRegistrationRepository.class)
65+
static class ReactiveOAuth2ClientConfiguration {
66+
67+
@Bean
68+
@ConditionalOnMissingBean
69+
public ReactiveOAuth2AuthorizedClientService authorizedClientService(
70+
ReactiveClientRegistrationRepository clientRegistrationRepository) {
71+
return new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository);
72+
}
73+
74+
@Bean
75+
@ConditionalOnMissingBean
76+
public ServerOAuth2AuthorizedClientRepository authorizedClientRepository(
77+
ReactiveOAuth2AuthorizedClientService authorizedClientService) {
78+
return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService);
79+
}
80+
81+
@Configuration
82+
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
83+
static class SecurityWebFilterChainConfiguration {
84+
85+
@Bean
86+
@ConditionalOnMissingBean
87+
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
88+
http.authorizeExchange().anyExchange().authenticated();
89+
http.oauth2Login();
90+
return http.build();
91+
}
92+
93+
}
94+
95+
}
96+
97+
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfigurationTests.java

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,27 @@
1818
import java.time.Duration;
1919
import java.util.ArrayList;
2020
import java.util.List;
21+
import java.util.stream.Collectors;
2122

2223
import org.junit.Test;
2324
import reactor.core.publisher.Flux;
2425

26+
import org.springframework.beans.BeansException;
2527
import org.springframework.boot.autoconfigure.AutoConfigurations;
28+
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
2629
import org.springframework.boot.test.context.FilteredClassLoader;
30+
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
2731
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
32+
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
2833
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
34+
import org.springframework.context.ApplicationContext;
35+
import org.springframework.context.ApplicationContextAware;
2936
import org.springframework.context.annotation.Bean;
3037
import org.springframework.context.annotation.Configuration;
3138
import org.springframework.context.annotation.Import;
39+
import org.springframework.security.config.BeanIds;
3240
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
41+
import org.springframework.security.config.web.server.ServerHttpSecurity;
3342
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
3443
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
3544
import org.springframework.security.oauth2.client.registration.ClientRegistration;
@@ -38,7 +47,11 @@
3847
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
3948
import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
4049
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
50+
import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter;
4151
import org.springframework.security.oauth2.core.AuthorizationGrantType;
52+
import org.springframework.security.web.server.SecurityWebFilterChain;
53+
import org.springframework.test.util.ReflectionTestUtils;
54+
import org.springframework.web.server.WebFilter;
4255

4356
import static org.assertj.core.api.Assertions.assertThat;
4457

@@ -49,8 +62,8 @@
4962
*/
5063
public class ReactiveOAuth2ClientAutoConfigurationTests {
5164

52-
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
53-
.withConfiguration(AutoConfigurations.of(ReactiveOAuth2ClientAutoConfiguration.class));
65+
private ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations
66+
.of(ReactiveOAuth2ClientAutoConfiguration.class, ReactiveSecurityAutoConfiguration.class));
5467

5568
private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration";
5669

@@ -82,15 +95,19 @@ public void clientRegistrationRepositoryBeanShouldBeCreatedWhenPropertiesPresent
8295
}
8396

8497
@Test
85-
public void authorizedClientServiceBeanIsConditionalOnClientRegistrationRepository() {
86-
this.contextRunner
87-
.run((context) -> assertThat(context).doesNotHaveBean(ReactiveOAuth2AuthorizedClientService.class));
98+
public void authorizedClientServiceAndRepositoryBeansAreConditionalOnClientRegistrationRepository() {
99+
this.contextRunner.run((context) -> {
100+
assertThat(context).doesNotHaveBean(ReactiveOAuth2AuthorizedClientService.class);
101+
assertThat(context).doesNotHaveBean(ServerOAuth2AuthorizedClientRepository.class);
102+
});
88103
}
89104

90105
@Test
91-
public void configurationRegistersAuthorizedClientServiceBean() {
92-
this.contextRunner.withUserConfiguration(ReactiveClientRepositoryConfiguration.class).run(
93-
(context) -> assertThat(context).hasSingleBean(InMemoryReactiveClientRegistrationRepository.class));
106+
public void configurationRegistersAuthorizedClientServiceAndRepositoryBeans() {
107+
this.contextRunner.withUserConfiguration(ReactiveClientRepositoryConfiguration.class).run((context) -> {
108+
assertThat(context).hasSingleBean(InMemoryReactiveOAuth2AuthorizedClientService.class);
109+
assertThat(context).hasSingleBean(AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository.class);
110+
});
94111
}
95112

96113
@Test
@@ -124,6 +141,22 @@ public void authorizedClientRepositoryBeanIsConditionalOnMissingBean() {
124141
});
125142
}
126143

144+
@Test
145+
public void securityWebFilterChainBeanConditionalOnWebApplication() {
146+
this.contextRunner.withUserConfiguration(ReactiveOAuth2AuthorizedClientRepositoryConfiguration.class)
147+
.run((context) -> assertThat(context).doesNotHaveBean(SecurityWebFilterChain.class));
148+
}
149+
150+
@Test
151+
public void configurationRegistersSecurityWebFilterChainBean() { // gh-17949
152+
new ReactiveWebApplicationContextRunner()
153+
.withConfiguration(AutoConfigurations.of(ReactiveOAuth2ClientAutoConfiguration.class))
154+
.withUserConfiguration(ReactiveOAuth2AuthorizedClientServiceConfiguration.class,
155+
ServerHttpSecurityConfiguration.class)
156+
.run((context) -> assertThat(getFilters(context, OAuth2LoginAuthenticationWebFilter.class))
157+
.isNotNull());
158+
}
159+
127160
@Test
128161
public void autoConfigurationConditionalOnClassFlux() {
129162
assertWhenClassNotPresent(Flux.class);
@@ -147,6 +180,15 @@ private void assertWhenClassNotPresent(Class<?> classToFilter) {
147180
.run((context) -> assertThat(context).doesNotHaveBean(ReactiveOAuth2ClientAutoConfiguration.class));
148181
}
149182

183+
@SuppressWarnings("unchecked")
184+
private List<WebFilter> getFilters(AssertableReactiveWebApplicationContext context,
185+
Class<? extends WebFilter> filter) {
186+
SecurityWebFilterChain filterChain = (SecurityWebFilterChain) context
187+
.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
188+
List<WebFilter> filters = (List<WebFilter>) ReflectionTestUtils.getField(filterChain, "filters");
189+
return filters.stream().filter(filter::isInstance).collect(Collectors.toList());
190+
}
191+
150192
@Configuration
151193
static class ReactiveClientRepositoryConfiguration {
152194

@@ -196,4 +238,24 @@ public ServerOAuth2AuthorizedClientRepository testAuthorizedClientRepository(
196238

197239
}
198240

241+
@Configuration
242+
static class ServerHttpSecurityConfiguration {
243+
244+
@Bean
245+
ServerHttpSecurity http() {
246+
TestServerHttpSecurity httpSecurity = new TestServerHttpSecurity();
247+
return httpSecurity;
248+
}
249+
250+
static class TestServerHttpSecurity extends ServerHttpSecurity implements ApplicationContextAware {
251+
252+
@Override
253+
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
254+
super.setApplicationContext(applicationContext);
255+
}
256+
257+
}
258+
259+
}
260+
199261
}

spring-boot-samples/spring-boot-sample-reactive-oauth2-client/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
</properties>
1717
<dependencies>
1818
<!-- Compile -->
19+
<dependency>
20+
<groupId>org.springframework.boot</groupId>
21+
<artifactId>spring-boot-starter-actuator</artifactId>
22+
</dependency>
1923
<dependency>
2024
<groupId>org.springframework.boot</groupId>
2125
<artifactId>spring-boot-starter-oauth2-client</artifactId>

spring-boot-samples/spring-boot-sample-reactive-oauth2-client/src/test/java/sample/oauth2/client/SampleReactiveOAuth2ClientApplicationTests.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,10 @@ public void loginShouldHaveBothOAuthClientsToChooseFrom() {
5151
assertThat(bodyString).contains("/oauth2/authorization/github-client-2");
5252
}
5353

54+
@Test
55+
public void actuatorShouldBeSecuredByOAuth() {
56+
this.webTestClient.get().uri("/actuator/health").exchange().expectStatus().isFound().expectHeader()
57+
.valueEquals("Location", "/login");
58+
}
59+
5460
}

0 commit comments

Comments
 (0)