diff --git a/docs/manual/src/docs/asciidoc/_includes/test.adoc b/docs/manual/src/docs/asciidoc/_includes/test.adoc index 7e3704e15d..381b03fa60 100644 --- a/docs/manual/src/docs/asciidoc/_includes/test.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/test.adoc @@ -693,3 +693,158 @@ mvc .perform(formLogin().user("admin").roles("USER","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 <> using the same setup and annotations we did in <>. +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") + ... +---- diff --git a/docs/manual/src/docs/asciidoc/index.adoc b/docs/manual/src/docs/asciidoc/index.adoc index a21b1791d4..7c5e1627c6 100644 --- a/docs/manual/src/docs/asciidoc/index.adoc +++ b/docs/manual/src/docs/asciidoc/index.adoc @@ -384,7 +384,10 @@ Below are the highlights of this milestone release. === 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/4128[#4128] - Initial Reactive Support (start with {gh-samples-url}/javaconfig/hellowebflux[hellowebflux]) +* Reactive Support +** <> +** <> +** <> [[samples]] == Samples and Guides (Start Here) @@ -763,6 +766,77 @@ a status code 200 will be returned by default. - Section <> (CAS protocol) - Documentation for the <> 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]] === Authentication @@ -1059,6 +1133,77 @@ 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]. +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 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 <>, 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 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.