Separate Servlet and WebFlux

Fixes: gh-5836
This commit is contained in:
Rob Winch 2018-09-11 21:01:07 -05:00
parent ed9cd478ba
commit 57359058dd
59 changed files with 204 additions and 190 deletions

View File

@ -0,0 +1,7 @@
= Reactive Applications
include::webflux.adoc[leveloffset=+1]
include::method.adoc[leveloffset=+1]
include::webtestclient.adoc[leveloffset=+1]

View File

@ -0,0 +1,103 @@
[[jc-erms]]
= EnableReactiveMethodSecurity
Spring Security supports method security using https://projectreactor.io/docs/core/release/reference/#context[Reactor's Context] which is setup using `ReactiveSecurityContextHolder`.
For example, this demonstrates how to retrieve the currently logged in user's message.
[NOTE]
====
For this to work the return type of the method must be a `org.reactivestreams.Publisher` (i.e. `Mono`/`Flux`).
This is necessary to integrate with Reactor's `Context`.
====
[source,java]
----
Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
Mono<String> messageByUsername = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Authentication::getName)
.flatMap(this::findMessageByUsername)
// In a WebFlux application the `subscriberContext` is automatically setup using `ReactorContextWebFilter`
.subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication));
StepVerifier.create(messageByUsername)
.expectNext("Hi user")
.verifyComplete();
----
with `this::findMessageByUsername` defined as:
[source,java]
----
Mono<String> findMessageByUsername(String username) {
return Mono.just("Hi " + username);
}
----
Below is a minimal method security configuration when using method security in reactive applications.
[source,java]
----
@EnableReactiveMethodSecurity
public class SecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build();
UserDetails admin = userBuilder.username("admin").password("admin").roles("USER","ADMIN").build();
return new MapReactiveUserDetailsService(rob, admin);
}
}
----
Consider the following class:
[source,java]
----
@Component
public class HelloWorldMessageService {
@PreAuthorize("hasRole('ADMIN')")
public Mono<String> findMessage() {
return Mono.just("Hello World!");
}
}
----
Combined with our configuration above, `@PreAuthorize("hasRole('ADMIN')")` will ensure that `findByMessage` is only invoked by a user with the role `ADMIN`.
It is important to note that any of the expressions in standard method security work for `@EnableReactiveMethodSecurity`.
However, at this time we only support return type of `Boolean` or `boolean` of the expression.
This means that the expression must not block.
When integrating with <<jc-webflux>>, the Reactor Context is automatically established by Spring Security according to the authenticated user.
[source,java]
----
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
return http
// Demonstrate that method security works
// Best practice to use both for defense in depth
.authorizeExchange()
.anyExchange().permitAll()
.and()
.httpBasic().and()
.build();
}
@Bean
MapReactiveUserDetailsService userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build();
UserDetails admin = userBuilder.username("admin").password("admin").roles("USER","ADMIN").build();
return new MapReactiveUserDetailsService(rob, admin);
}
}
----
You can find a complete sample in {gh-samples-url}/javaconfig/hellowebflux-method[hellowebflux-method]

View File

@ -0,0 +1,68 @@
[[jc-webflux]]
= WebFlux Security
Spring Security's WebFlux support relies on a `WebFilter` and works the same for Spring WebFlux and Spring WebFlux.Fn.
You can find a few sample applications that demonstrate the code below:
* Hello WebFlux {gh-samples-url}/javaconfig/hellowebflux[hellowebflux]
* Hello WebFlux.Fn {gh-samples-url}/javaconfig/hellowebfluxfn[hellowebfluxfn]
* Hello WebFlux Method {gh-samples-url}/javaconfig/hellowebflux-method[hellowebflux-method]
== Minimal WebFlux Security Configuration
You can find a minimal WebFlux Security configuration below:
[source,java]
-----
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
}
-----
This configuration provides form and http basic authentication, sets up authorization to require an authenticated user for accessing any page, sets up a default log in page and a default log out page, sets up security related HTTP headers, CSRF protection, and more.
== Explicit WebFlux Security Configuration
You can find an explicit version of the minimal WebFlux Security configuration below:
[source,java]
-----
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.httpBasic().and()
.formLogin();
return http.build();
}
}
-----
This configuration explicitly sets up all the same things as our minimal configuration.
From here you can easily make the changes to the defaults.

View File

@ -1,8 +1,8 @@
[[test-webflux]]
== WebFlux Support
= WebFlux Support
[[test-erms]]
=== Reactive Method Security
== Reactive Method Security
For example, we can test our example from <<jc-erms>> using the same setup and annotations we did in <<test-method>>.
Here is a minimal sample of what we can do:
@ -41,7 +41,7 @@ public class HelloWorldMessageServiceTests {
----
[[test-webtestclient]]
=== WebTestClientSupport
== WebTestClientSupport
Spring Security provides integration with `WebTestClient`.
The basic setup looks like this:
@ -70,7 +70,7 @@ public class HelloWebfluxMethodApplicationTests {
}
----
==== Authentication
=== Authentication
After applying the Spring Security support to `WebTestClient` we can use either annotations or `mutateWith` support.
For example:
@ -134,7 +134,7 @@ public void messageWhenMutateWithMockAdminThenOk() throws Exception {
----
==== CSRF Support
=== CSRF Support
Spring Security also provides support for CSRF testing with `WebTestClient`.
For example:

View File

@ -0,0 +1,17 @@
= Servlet Applications
include::preface/index.adoc[leveloffset=+1]
include::architecture/index.adoc[leveloffset=+1]
include::test/index.adoc[leveloffset=+1]
include::web/index.adoc[leveloffset=+1]
include::authorization/index.adoc[leveloffset=+1]
include::additional-topics/index.adoc[leveloffset=+1]
include::data/index.adoc[leveloffset=+1]
include::appendix/index.adoc[leveloffset=+1]

View File

@ -383,74 +383,6 @@ If not configured a status code 200 will be returned by default.
- Section <<cas-singlelogout, Single Logout>> (CAS protocol)
- Documentation for the <<nsa-logout, logout element>> in the Spring Security XML Namespace section
[[jc-webflux]]
=== WebFlux Security
Spring Security's WebFlux support relies on a `WebFilter` and works the same for Spring WebFlux and Spring WebFlux.Fn.
You can find a few sample applications that demonstrate the code below:
* Hello WebFlux {gh-samples-url}/javaconfig/hellowebflux[hellowebflux]
* Hello WebFlux.Fn {gh-samples-url}/javaconfig/hellowebfluxfn[hellowebfluxfn]
* Hello WebFlux Method {gh-samples-url}/javaconfig/hellowebflux-method[hellowebflux-method]
==== Minimal WebFlux Security Configuration
You can find a minimal WebFlux Security configuration below:
[source,java]
-----
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
}
-----
This configuration provides form and http basic authentication, sets up authorization to require an authenticated user for accessing any page, sets up a default log in page and a default log out page, sets up security related HTTP headers, CSRF protection, and more.
==== Explicit WebFlux Security Configuration
You can find an explicit version of the minimal WebFlux Security configuration below:
[source,java]
-----
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.httpBasic().and()
.formLogin();
return http.build();
}
}
-----
This configuration explicitly sets up all the same things as our minimal configuration.
From here you can easily make the changes to the defaults.
[[jc-oauth2login]]
=== OAuth 2.0 Login
@ -1302,110 +1234,6 @@ public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
For additional information about methods that can be overridden, refer to the `GlobalMethodSecurityConfiguration` Javadoc.
[[jc-erms]]
==== EnableReactiveMethodSecurity
Spring Security supports method security using https://projectreactor.io/docs/core/release/reference/#context[Reactor's Context] which is setup using `ReactiveSecurityContextHolder`.
For example, this demonstrates how to retrieve the currently logged in user's message.
[NOTE]
====
For this to work the return type of the method must be a `org.reactivestreams.Publisher` (i.e. `Mono`/`Flux`).
This is necessary to integrate with Reactor's `Context`.
====
[source,java]
----
Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
Mono<String> messageByUsername = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Authentication::getName)
.flatMap(this::findMessageByUsername)
// In a WebFlux application the `subscriberContext` is automatically setup using `ReactorContextWebFilter`
.subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication));
StepVerifier.create(messageByUsername)
.expectNext("Hi user")
.verifyComplete();
----
with `this::findMessageByUsername` defined as:
[source,java]
----
Mono<String> findMessageByUsername(String username) {
return Mono.just("Hi " + username);
}
----
Below is a minimal method security configuration when using method security in reactive applications.
[source,java]
----
@EnableReactiveMethodSecurity
public class SecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build();
UserDetails admin = userBuilder.username("admin").password("admin").roles("USER","ADMIN").build();
return new MapReactiveUserDetailsService(rob, admin);
}
}
----
Consider the following class:
[source,java]
----
@Component
public class HelloWorldMessageService {
@PreAuthorize("hasRole('ADMIN')")
public Mono<String> findMessage() {
return Mono.just("Hello World!");
}
}
----
Combined with our configuration above, `@PreAuthorize("hasRole('ADMIN')")` will ensure that `findByMessage` is only invoked by a user with the role `ADMIN`.
It is important to note that any of the expressions in standard method security work for `@EnableReactiveMethodSecurity`.
However, at this time we only support return type of `Boolean` or `boolean` of the expression.
This means that the expression must not block.
When integrating with <<jc-webflux>>, the Reactor Context is automatically established by Spring Security according to the authenticated user.
[source,java]
----
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
return http
// Demonstrate that method security works
// Best practice to use both for defense in depth
.authorizeExchange()
.anyExchange().permitAll()
.and()
.httpBasic().and()
.build();
}
@Bean
MapReactiveUserDetailsService userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build();
UserDetails admin = userBuilder.username("admin").password("admin").roles("USER","ADMIN").build();
return new MapReactiveUserDetailsService(rob, admin);
}
}
----
You can find a complete sample in {gh-samples-url}/javaconfig/hellowebflux-method[hellowebflux-method]
=== Post Processing Configured Objects
Spring Security's Java Configuration does not expose every property of every object that it configures.

View File

@ -2,24 +2,15 @@
Ben Alex; Luke Taylor; Rob Winch; Gunnar Hillert; Joe Grandja; Jay Bryant
:include-dir: _includes
:security-api-url: http://docs.spring.io/spring-security/site/docs/current/apidocs/
:source-indent: 0
:tabsize: 2
Spring Security is a powerful and highly customizable authentication and access-control framework.
It is the de-facto standard for securing Spring-based applications.
include::{include-dir}/preface/index.adoc[]
include::{include-dir}/architecture/index.adoc[]
include::{include-dir}/servlet/index.adoc[]
include::{include-dir}/test/index.adoc[]
include::{include-dir}/web/index.adoc[]
include::{include-dir}/authorization/index.adoc[]
include::{include-dir}/additional-topics/index.adoc[]
include::{include-dir}/data/index.adoc[]
include::{include-dir}/appendix/index.adoc[]
include::{include-dir}/reactive/index.adoc[]