Skip to content

Commit 5d05347

Browse files
committed
Add auto-config and starter for reactive security
Closes gh-9925
1 parent 1e11f80 commit 5d05347

File tree

17 files changed

+601
-0
lines changed

17 files changed

+601
-0
lines changed

spring-boot-autoconfigure/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,11 @@
527527
<artifactId>spring-security-data</artifactId>
528528
<optional>true</optional>
529529
</dependency>
530+
<dependency>
531+
<groupId>org.springframework.security</groupId>
532+
<artifactId>spring-security-webflux</artifactId>
533+
<optional>true</optional>
534+
</dependency>
530535
<dependency>
531536
<groupId>org.springframework.session</groupId>
532537
<artifactId>spring-session-core</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2012-2017 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+
* http://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.reactive;
18+
19+
import java.util.UUID;
20+
21+
import org.apache.commons.logging.Log;
22+
import org.apache.commons.logging.LogFactory;
23+
24+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
25+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
26+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.security.authentication.ReactiveAuthenticationManager;
30+
import org.springframework.security.core.userdetails.MapUserDetailsRepository;
31+
import org.springframework.security.core.userdetails.User;
32+
import org.springframework.security.core.userdetails.UserDetails;
33+
import org.springframework.security.core.userdetails.UserDetailsRepository;
34+
35+
/**
36+
* Default user {@link Configuration} for a reactive web application.
37+
* Configures a {@link UserDetailsRepository} with a default user and generated password.
38+
* This backs-off completely if there is a bean of type {@link UserDetailsRepository}
39+
* or {@link ReactiveAuthenticationManager}.
40+
*
41+
* @author Madhura Bhave
42+
* @since 2.0.0
43+
*/
44+
@Configuration
45+
@ConditionalOnClass({ReactiveAuthenticationManager.class})
46+
@ConditionalOnMissingBean({ReactiveAuthenticationManager.class, UserDetailsRepository.class })
47+
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
48+
public class ReactiveAuthenticationManagerConfiguration {
49+
50+
private static final Log logger = LogFactory
51+
.getLog(ReactiveAuthenticationManagerConfiguration.class);
52+
53+
@Bean
54+
public MapUserDetailsRepository userDetailsRepository() {
55+
String password = UUID.randomUUID().toString();
56+
logger.info(
57+
String.format("%n%nUsing default security password: %s%n", password));
58+
UserDetails user = User.withUsername("user")
59+
.password(password)
60+
.roles()
61+
.build();
62+
return new MapUserDetailsRepository(user);
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2012-2017 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+
* http://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.reactive;
18+
19+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
20+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
21+
import org.springframework.context.annotation.Configuration;
22+
import org.springframework.context.annotation.Import;
23+
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
24+
import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver;
25+
26+
/**
27+
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security in a
28+
* reactive application. This auto-configuration adds {@link EnableWebFluxSecurity}
29+
* and delegates to Spring Security's content-negotiation mechanism for authentication.
30+
* In a webapp this configuration also secures all web endpoints.
31+
*
32+
* @author Madhura Bhave
33+
* @since 2.0.0
34+
*/
35+
@Configuration
36+
@ConditionalOnClass({EnableWebFluxSecurity.class, AuthenticationPrincipalArgumentResolver.class})
37+
@Import({ WebfluxSecurityConfiguration.class,
38+
ReactiveAuthenticationManagerConfiguration.class })
39+
public class ReactiveSecurityAutoConfiguration {
40+
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2012-2017 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+
* http://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.reactive;
18+
19+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
20+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
22+
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
23+
import org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration;
24+
25+
/**
26+
* Switches on {@link EnableWebFluxSecurity} for a reactive web application
27+
* if this annotation has not been added by the user.
28+
*
29+
* @author Madhura Bhave
30+
* @since 2.0.0
31+
*/
32+
@ConditionalOnClass(EnableWebFluxSecurity.class)
33+
@ConditionalOnMissingBean(WebFluxSecurityConfiguration.class)
34+
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
35+
@EnableWebFluxSecurity
36+
public class WebfluxSecurityConfiguration {
37+
38+
}

spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
9999
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
100100
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
101101
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
102+
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
102103
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
103104
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
104105
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright 2012-2017 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+
* http://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.reactive;
18+
19+
import org.junit.Test;
20+
import reactor.core.publisher.Mono;
21+
22+
import org.springframework.boot.autoconfigure.AutoConfigurations;
23+
import org.springframework.boot.autoconfigure.web.reactive.MockReactiveWebServerFactory;
24+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
25+
import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext;
26+
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
27+
import org.springframework.context.ApplicationContext;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.Configuration;
30+
import org.springframework.http.server.reactive.HttpHandler;
31+
import org.springframework.security.authentication.ReactiveAuthenticationManager;
32+
import org.springframework.security.config.annotation.web.reactive.HttpSecurityConfiguration;
33+
import org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration;
34+
import org.springframework.security.core.Authentication;
35+
import org.springframework.security.core.userdetails.MapUserDetailsRepository;
36+
import org.springframework.security.core.userdetails.User;
37+
import org.springframework.security.core.userdetails.UserDetails;
38+
import org.springframework.security.core.userdetails.UserDetailsRepository;
39+
import org.springframework.security.web.server.WebFilterChainFilter;
40+
import org.springframework.web.reactive.config.EnableWebFlux;
41+
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
42+
43+
import static org.assertj.core.api.Assertions.assertThat;
44+
45+
/**
46+
* Tests for {@link ReactiveSecurityAutoConfiguration}.
47+
*
48+
* @author Madhura Bhave
49+
*/
50+
public class ReactiveSecurityAutoConfigurationTests {
51+
52+
private ApplicationContextRunner contextRunner = new ApplicationContextRunner(ReactiveWebServerApplicationContext::new);
53+
54+
@Test
55+
public void enablesWebFluxSecurity() {
56+
this.contextRunner.withUserConfiguration(TestConfig.class)
57+
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class))
58+
.run(context -> {
59+
assertThat(context).getBean(HttpSecurityConfiguration.class).isNotNull();
60+
assertThat(context).getBean(WebFluxSecurityConfiguration.class).isNotNull();
61+
assertThat(context).getBean(WebFilterChainFilter.class).isNotNull();
62+
});
63+
}
64+
65+
@Test
66+
public void configuresADefaultUser() {
67+
this.contextRunner.withUserConfiguration(TestConfig.class)
68+
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class))
69+
.run(context -> {
70+
UserDetailsRepository userDetailsRepository = context.getBean(UserDetailsRepository.class);
71+
assertThat(userDetailsRepository.findByUsername("user").block()).isNotNull();
72+
});
73+
}
74+
75+
@Test
76+
public void doesNotConfigureDefaultUserIfUserDetailsRepositoryAvailable() {
77+
this.contextRunner.withUserConfiguration(UserConfig.class, TestConfig.class)
78+
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class))
79+
.run(context -> {
80+
UserDetailsRepository userDetailsRepository = context.getBean(UserDetailsRepository.class);
81+
assertThat(userDetailsRepository.findByUsername("user").block()).isNull();
82+
assertThat(userDetailsRepository.findByUsername("foo").block()).isNotNull();
83+
assertThat(userDetailsRepository.findByUsername("admin").block()).isNotNull();
84+
});
85+
}
86+
87+
@Test
88+
public void doesNotConfigureDefaultUserIfAuthenticationManagerAvailable() {
89+
this.contextRunner.withUserConfiguration(AuthenticationManagerConfig.class, TestConfig.class)
90+
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class))
91+
.run(context -> {
92+
assertThat(context).getBean(UserDetailsRepository.class).isNull();
93+
});
94+
}
95+
96+
@Configuration
97+
@EnableWebFlux
98+
static class TestConfig {
99+
100+
@Bean
101+
public HttpHandler httpHandler(ApplicationContext applicationContext) {
102+
return WebHttpHandlerBuilder.applicationContext(applicationContext).build();
103+
}
104+
105+
@Bean
106+
public ReactiveWebServerFactory reactiveWebServerFactory() {
107+
return new MockReactiveWebServerFactory();
108+
}
109+
110+
}
111+
112+
@Configuration
113+
static class UserConfig {
114+
115+
@Bean
116+
public MapUserDetailsRepository userDetailsRepository() {
117+
UserDetails foo = User.withUsername("foo").password("foo").roles("USER").build();
118+
UserDetails admin = User.withUsername("admin").password("admin").roles("USER", "ADMIN").build();
119+
return new MapUserDetailsRepository(foo, admin);
120+
}
121+
122+
}
123+
124+
@Configuration
125+
static class AuthenticationManagerConfig {
126+
127+
@Bean
128+
public ReactiveAuthenticationManager reactiveAuthenticationManager() {
129+
return new ReactiveAuthenticationManager() {
130+
@Override
131+
public Mono<Authentication> authenticate(Authentication authentication) {
132+
return null;
133+
}
134+
};
135+
}
136+
137+
}
138+
139+
}

spring-boot-dependencies/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,11 @@
506506
<artifactId>spring-boot-starter-security</artifactId>
507507
<version>2.0.0.BUILD-SNAPSHOT</version>
508508
</dependency>
509+
<dependency>
510+
<groupId>org.springframework.boot</groupId>
511+
<artifactId>spring-boot-starter-security-reactive</artifactId>
512+
<version>2.0.0.BUILD-SNAPSHOT</version>
513+
</dependency>
509514
<dependency>
510515
<groupId>org.springframework.boot</groupId>
511516
<artifactId>spring-boot-starter-social-facebook</artifactId>

spring-boot-samples/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
<module>spring-boot-sample-property-validation</module>
6868
<module>spring-boot-sample-quartz</module>
6969
<module>spring-boot-sample-secure</module>
70+
<module>spring-boot-sample-secure-webflux</module>
7071
<module>spring-boot-sample-servlet</module>
7172
<module>spring-boot-sample-session</module>
7273
<module>spring-boot-sample-simple</module>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<parent>
5+
<!-- Your own application should inherit from spring-boot-starter-parent -->
6+
<groupId>org.springframework.boot</groupId>
7+
<artifactId>spring-boot-samples</artifactId>
8+
<version>2.0.0.BUILD-SNAPSHOT</version>
9+
</parent>
10+
<artifactId>spring-boot-sample-secure-webflux</artifactId>
11+
<name>Spring Boot Secure WebFlux Sample</name>
12+
<description>Spring Boot Secure WebFlux Sample</description>
13+
<url>http://projects.spring.io/spring-boot/</url>
14+
<organization>
15+
<name>Pivotal Software, Inc.</name>
16+
<url>http://www.spring.io</url>
17+
</organization>
18+
<properties>
19+
<main.basedir>${basedir}/../..</main.basedir>
20+
<m2eclipse.wtp.contextRoot>/</m2eclipse.wtp.contextRoot>
21+
</properties>
22+
<dependencies>
23+
<!-- Compile -->
24+
<dependency>
25+
<groupId>org.springframework.boot</groupId>
26+
<artifactId>spring-boot-starter-webflux</artifactId>
27+
</dependency>
28+
<dependency>
29+
<groupId>org.springframework.boot</groupId>
30+
<artifactId>spring-boot-starter-actuator</artifactId>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.springframework.boot</groupId>
34+
<artifactId>spring-boot-starter-security-reactive</artifactId>
35+
</dependency>
36+
<!-- Test -->
37+
<dependency>
38+
<groupId>org.springframework.boot</groupId>
39+
<artifactId>spring-boot-starter-test</artifactId>
40+
<scope>test</scope>
41+
</dependency>
42+
<dependency>
43+
<groupId>io.projectreactor</groupId>
44+
<artifactId>reactor-test</artifactId>
45+
<scope>test</scope>
46+
</dependency>
47+
</dependencies>
48+
<build>
49+
<plugins>
50+
<plugin>
51+
<groupId>org.springframework.boot</groupId>
52+
<artifactId>spring-boot-maven-plugin</artifactId>
53+
<executions>
54+
<execution>
55+
<id>generate build info</id>
56+
<goals>
57+
<goal>build-info</goal>
58+
</goals>
59+
</execution>
60+
</executions>
61+
</plugin>
62+
</plugins>
63+
</build>
64+
</project>

0 commit comments

Comments
 (0)