Add WebFlux to What's New 5.0

Fixes gh-4757
This commit is contained in:
Rob Winch 2017-10-30 14:58:33 -05:00
parent d435f149eb
commit 8e6c726fb2
2 changed files with 301 additions and 1 deletions

View File

@ -693,3 +693,158 @@ mvc
.perform(formLogin().user("admin").roles("USER","ADMIN")) .perform(formLogin().user("admin").roles("USER","ADMIN"))
.andExpect(authenticated().withUsername("admin")); .andExpect(authenticated().withUsername("admin"));
---- ----
[[test-webflux]]
== WebFlux Support
Spring Security provides test integration with Spring WebFlux for both method security and WebFlux.
You can find a complete working sample at {gh-samples-url}/javaconfig/hellowebflux-method[hellowebflux-method]
[[test-erms]]
=== 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:
[source,java]
----
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = HelloWebfluxMethodApplication.class)
public class HelloWorldMessageServiceTests {
@Autowired
HelloWorldMessageService messages;
@Test
public void messagesWhenNotAuthenticatedThenDenied() {
StepVerifier.create(this.messages.findMessage())
.expectError(AccessDeniedException.class)
.verify();
}
@Test
@WithMockUser
public void messagesWhenUserThenDenied() {
StepVerifier.create(this.messages.findMessage())
.expectError(AccessDeniedException.class)
.verify();
}
@Test
@WithMockUser(roles = "ADMIN")
public void messagesWhenAdminThenOk() {
StepVerifier.create(this.messages.findMessage())
.expectNext("Hello World!")
.verifyComplete();
}
}
----
[[test-webtestclient]]
=== WebTestClientSupport
Spring Security provides integration with `WebTestClient`.
The basic setup looks like this:
[source,java]
----
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = HelloWebfluxMethodApplication.class)
public class HelloWebfluxMethodApplicationTests {
@Autowired
ApplicationContext context;
WebTestClient rest;
@Before
public void setup() {
this.rest = WebTestClient
.bindToApplicationContext(this.context)
// add Spring Security test Support
.apply(springSecurity())
.configureClient()
.filter(basicAuthentication())
.build();
}
// ...
}
----
==== Authentication
After applying the Spring Security support to `WebTestClient` we can use either annotations or `mutateWith` support.
For example:
[source,java]
----
@Test
public void messageWhenNotAuthenticated() throws Exception {
this.rest
.get()
.uri("/message")
.exchange()
.expectStatus().isUnauthorized();
}
// --- WithMockUser ---
@Test
@WithMockUser
public void messageWhenWithMockUserThenForbidden() throws Exception {
this.rest
.get()
.uri("/message")
.exchange()
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN);
}
@Test
@WithMockUser(roles = "ADMIN")
public void messageWhenWithMockAdminThenOk() throws Exception {
this.rest
.get()
.uri("/message")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World!");
}
// --- mutateWith mockUser ---
@Test
public void messageWhenMutateWithMockUserThenForbidden() throws Exception {
this.rest
.mutateWith(mockUser())
.get()
.uri("/message")
.exchange()
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN);
}
@Test
public void messageWhenMutateWithMockAdminThenOk() throws Exception {
this.rest
.mutateWith(mockUser().roles("ADMIN"))
.get()
.uri("/message")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World!");
}
----
==== CSRF Support
Spring Security also provides support for CSRF testing with `WebTestClient`.
For example:
[source,java]
----
this.rest
// provide a valid CSRF token
.mutateWith(csrf())
.post()
.uri("/login")
...
----

View File

@ -384,7 +384,10 @@ Below are the highlights of this milestone release.
=== New Features === New Features
* https://github.com/spring-projects/spring-security/issues/3907[#3907] - Support added for OAuth 2.0 Login (start with {gh-samples-url}/boot/oauth2login/README.adoc[Sample README]) * https://github.com/spring-projects/spring-security/issues/3907[#3907] - Support added for OAuth 2.0 Login (start with {gh-samples-url}/boot/oauth2login/README.adoc[Sample README])
* https://github.com/spring-projects/spring-security/issues/4128[#4128] - Initial Reactive Support (start with {gh-samples-url}/javaconfig/hellowebflux[hellowebflux]) * Reactive Support
** <<jc-webflux,@EnableWebFluxSecurity>>
** <<jc-erms,@EnableReactiveMethodSecurity>>
** <<test-webflux,WebFlux Testing Support>>
[[samples]] [[samples]]
== Samples and Guides (Start Here) == Samples and Guides (Start Here)
@ -763,6 +766,77 @@ a status code 200 will be returned by default.
- Section <<cas-singlelogout, Single Logout>> (CAS protocol) - Section <<cas-singlelogout, Single Logout>> (CAS protocol)
- Documentation for the <<nsa-logout, logout element>> in the Spring Security XML Namespace section - 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 userDetailsRepository() {
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 userDetailsRepository() {
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 sames things as our minimal configuration.
From here you can easily make the changes to the defaults.
[[jc-authentication]] [[jc-authentication]]
=== Authentication === Authentication
@ -1059,6 +1133,77 @@ public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
For additional information about methods that can be overridden, refer to the `GlobalMethodSecurityConfiguration` Javadoc. 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].
Below is a minimal method security configuration when using method security in reactive applications.
[source,java]
----
@EnableReactiveMethodSecurity
public class SecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsRepository() {
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 userDetailsRepository() {
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 === Post Processing Configured Objects
Spring Security's Java Configuration does not expose every property of every object that it configures. This simplifies the configuration for a majority of users. Afterall, if every property was exposed, users could use standard bean configuration. Spring Security's Java Configuration does not expose every property of every object that it configures. This simplifies the configuration for a majority of users. Afterall, if every property was exposed, users could use standard bean configuration.