Skip to content

Commit

Permalink
Add auto-config and starter for reactive security
Browse files Browse the repository at this point in the history
Closes gh-9925
  • Loading branch information
mbhave committed Sep 27, 2017
1 parent 1e11f80 commit 5d05347
Show file tree
Hide file tree
Showing 17 changed files with 601 additions and 0 deletions.
5 changes: 5 additions & 0 deletions spring-boot-autoconfigure/pom.xml
Expand Up @@ -527,6 +527,11 @@
<artifactId>spring-security-data</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-webflux</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
Expand Down
@@ -0,0 +1,64 @@
/*
* Copyright 2012-2017 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
*
* http://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.reactive;

import java.util.UUID;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.core.userdetails.MapUserDetailsRepository;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsRepository;

/**
* Default user {@link Configuration} for a reactive web application.
* Configures a {@link UserDetailsRepository} with a default user and generated password.
* This backs-off completely if there is a bean of type {@link UserDetailsRepository}
* or {@link ReactiveAuthenticationManager}.
*
* @author Madhura Bhave
* @since 2.0.0
*/
@Configuration
@ConditionalOnClass({ReactiveAuthenticationManager.class})
@ConditionalOnMissingBean({ReactiveAuthenticationManager.class, UserDetailsRepository.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class ReactiveAuthenticationManagerConfiguration {

private static final Log logger = LogFactory
.getLog(ReactiveAuthenticationManagerConfiguration.class);

@Bean
public MapUserDetailsRepository userDetailsRepository() {
String password = UUID.randomUUID().toString();
logger.info(
String.format("%n%nUsing default security password: %s%n", password));
UserDetails user = User.withUsername("user")
.password(password)
.roles()
.build();
return new MapUserDetailsRepository(user);
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2012-2017 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
*
* http://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.reactive;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
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.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver;

/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security in a
* reactive application. This auto-configuration adds {@link EnableWebFluxSecurity}
* and delegates to Spring Security's content-negotiation mechanism for authentication.
* In a webapp this configuration also secures all web endpoints.
*
* @author Madhura Bhave
* @since 2.0.0
*/
@Configuration
@ConditionalOnClass({EnableWebFluxSecurity.class, AuthenticationPrincipalArgumentResolver.class})
@Import({ WebfluxSecurityConfiguration.class,
ReactiveAuthenticationManagerConfiguration.class })
public class ReactiveSecurityAutoConfiguration {

}
@@ -0,0 +1,38 @@
/*
* Copyright 2012-2017 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
*
* http://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.reactive;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration;

/**
* Switches on {@link EnableWebFluxSecurity} for a reactive web application
* if this annotation has not been added by the user.
*
* @author Madhura Bhave
* @since 2.0.0
*/
@ConditionalOnClass(EnableWebFluxSecurity.class)
@ConditionalOnMissingBean(WebFluxSecurityConfiguration.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@EnableWebFluxSecurity
public class WebfluxSecurityConfiguration {

}
Expand Up @@ -99,6 +99,7 @@ org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
Expand Down
@@ -0,0 +1,139 @@
/*
* Copyright 2012-2017 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
*
* http://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.reactive;

import org.junit.Test;
import reactor.core.publisher.Mono;

import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.web.reactive.MockReactiveWebServerFactory;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext;
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.HttpSecurityConfiguration;
import org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.MapUserDetailsRepository;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsRepository;
import org.springframework.security.web.server.WebFilterChainFilter;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;

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

/**
* Tests for {@link ReactiveSecurityAutoConfiguration}.
*
* @author Madhura Bhave
*/
public class ReactiveSecurityAutoConfigurationTests {

private ApplicationContextRunner contextRunner = new ApplicationContextRunner(ReactiveWebServerApplicationContext::new);

@Test
public void enablesWebFluxSecurity() {
this.contextRunner.withUserConfiguration(TestConfig.class)
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class))
.run(context -> {
assertThat(context).getBean(HttpSecurityConfiguration.class).isNotNull();
assertThat(context).getBean(WebFluxSecurityConfiguration.class).isNotNull();
assertThat(context).getBean(WebFilterChainFilter.class).isNotNull();
});
}

@Test
public void configuresADefaultUser() {
this.contextRunner.withUserConfiguration(TestConfig.class)
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class))
.run(context -> {
UserDetailsRepository userDetailsRepository = context.getBean(UserDetailsRepository.class);
assertThat(userDetailsRepository.findByUsername("user").block()).isNotNull();
});
}

@Test
public void doesNotConfigureDefaultUserIfUserDetailsRepositoryAvailable() {
this.contextRunner.withUserConfiguration(UserConfig.class, TestConfig.class)
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class))
.run(context -> {
UserDetailsRepository userDetailsRepository = context.getBean(UserDetailsRepository.class);
assertThat(userDetailsRepository.findByUsername("user").block()).isNull();
assertThat(userDetailsRepository.findByUsername("foo").block()).isNotNull();
assertThat(userDetailsRepository.findByUsername("admin").block()).isNotNull();
});
}

@Test
public void doesNotConfigureDefaultUserIfAuthenticationManagerAvailable() {
this.contextRunner.withUserConfiguration(AuthenticationManagerConfig.class, TestConfig.class)
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class))
.run(context -> {
assertThat(context).getBean(UserDetailsRepository.class).isNull();
});
}

@Configuration
@EnableWebFlux
static class TestConfig {

@Bean
public HttpHandler httpHandler(ApplicationContext applicationContext) {
return WebHttpHandlerBuilder.applicationContext(applicationContext).build();
}

@Bean
public ReactiveWebServerFactory reactiveWebServerFactory() {
return new MockReactiveWebServerFactory();
}

}

@Configuration
static class UserConfig {

@Bean
public MapUserDetailsRepository userDetailsRepository() {
UserDetails foo = User.withUsername("foo").password("foo").roles("USER").build();
UserDetails admin = User.withUsername("admin").password("admin").roles("USER", "ADMIN").build();
return new MapUserDetailsRepository(foo, admin);
}

}

@Configuration
static class AuthenticationManagerConfig {

@Bean
public ReactiveAuthenticationManager reactiveAuthenticationManager() {
return new ReactiveAuthenticationManager() {
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
return null;
}
};
}

}

}
5 changes: 5 additions & 0 deletions spring-boot-dependencies/pom.xml
Expand Up @@ -506,6 +506,11 @@
<artifactId>spring-boot-starter-security</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security-reactive</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-social-facebook</artifactId>
Expand Down
1 change: 1 addition & 0 deletions spring-boot-samples/pom.xml
Expand Up @@ -67,6 +67,7 @@
<module>spring-boot-sample-property-validation</module>
<module>spring-boot-sample-quartz</module>
<module>spring-boot-sample-secure</module>
<module>spring-boot-sample-secure-webflux</module>
<module>spring-boot-sample-servlet</module>
<module>spring-boot-sample-session</module>
<module>spring-boot-sample-simple</module>
Expand Down
64 changes: 64 additions & 0 deletions spring-boot-samples/spring-boot-sample-secure-webflux/pom.xml
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-secure-webflux</artifactId>
<name>Spring Boot Secure WebFlux Sample</name>
<description>Spring Boot Secure WebFlux Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
<m2eclipse.wtp.contextRoot>/</m2eclipse.wtp.contextRoot>
</properties>
<dependencies>
<!-- Compile -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security-reactive</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>generate build info</id>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

0 comments on commit 5d05347

Please sign in to comment.