2
0
Эх сурвалжийг харах

Add WebFlux to What's New 5.0

Fixes gh-4757
Rob Winch 7 жил өмнө
parent
commit
8e6c726fb2

+ 155 - 0
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 <<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")
+	...
+----

+ 146 - 1
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
+** <<jc-webflux,@EnableWebFluxSecurity>>
+** <<jc-erms,@EnableReactiveMethodSecurity>>
+** <<test-webflux,WebFlux Testing Support>>
 
 [[samples]]
 == 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)
 - 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]]
 === 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<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
 
 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.