| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 | 
							- [[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`) or the function must be a Kotlin coroutine function.
 
- This is necessary to integrate with Reactor's `Context`.
 
- ====
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- 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();
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- val authentication: Authentication = TestingAuthenticationToken("user", "password", "ROLE_USER")
 
- val messageByUsername: Mono<String> = 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:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- Mono<String> findMessageByUsername(String username) {
 
- 	return Mono.just("Hi " + username);
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- fun findMessageByUsername(username: String): Mono<String> {
 
- 	return Mono.just("Hi $username")
 
- }
 
- ----
 
- ====
 
- Below is a minimal method security configuration when using method security in reactive applications.
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @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);
 
- 	}
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @EnableReactiveMethodSecurity
 
- class SecurityConfig {
 
- 	@Bean
 
- 	fun userDetailsService(): MapReactiveUserDetailsService {
 
- 		val userBuilder: User.UserBuilder = User.withDefaultPasswordEncoder()
 
- 		val rob = userBuilder.username("rob")
 
- 			.password("rob")
 
- 			.roles("USER")
 
- 			.build()
 
- 		val admin = userBuilder.username("admin")
 
- 			.password("admin")
 
- 			.roles("USER", "ADMIN")
 
- 			.build()
 
- 		return MapReactiveUserDetailsService(rob, admin)
 
- 	}
 
- }
 
- ----
 
- ====
 
- Consider the following class:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @Component
 
- public class HelloWorldMessageService {
 
- 	@PreAuthorize("hasRole('ADMIN')")
 
- 	public Mono<String> findMessage() {
 
- 		return Mono.just("Hello World!");
 
- 	}
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Component
 
- class HelloWorldMessageService {
 
- 	@PreAuthorize("hasRole('ADMIN')")
 
- 	fun findMessage(): Mono<String> {
 
- 		return Mono.just("Hello World!")
 
- 	}
 
- }
 
- ----
 
- ====
 
- Or, the following class using Kotlin coroutines:
 
- ====
 
- .Kotlin
 
- [source,kotlin,role="primary"]
 
- ----
 
- @Component
 
- class HelloWorldMessageService {
 
-     @PreAuthorize("hasRole('ADMIN')")
 
-     suspend fun findMessage(): String {
 
-         delay(10)
 
-         return "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 xref:reactive/webflux.adoc#jc-webflux[WebFlux Security], the Reactor Context is automatically established by Spring Security according to the authenticated user.
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @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(exchanges -> exchanges
 
- 				.anyExchange().permitAll()
 
- 			)
 
- 			.httpBasic(withDefaults())
 
- 			.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);
 
- 	}
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @EnableWebFluxSecurity
 
- @EnableReactiveMethodSecurity
 
- class SecurityConfig {
 
- 	@Bean
 
- 	open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 
- 		return http {
 
- 			authorizeExchange {
 
- 				authorize(anyExchange, permitAll)
 
- 			}
 
- 			httpBasic { }
 
- 		}
 
- 	}
 
- 	@Bean
 
- 	fun userDetailsService(): MapReactiveUserDetailsService {
 
- 		val userBuilder: User.UserBuilder = User.withDefaultPasswordEncoder()
 
- 		val rob = userBuilder.username("rob")
 
- 			.password("rob")
 
- 			.roles("USER")
 
- 			.build()
 
- 		val admin = userBuilder.username("admin")
 
- 			.password("admin")
 
- 			.roles("USER", "ADMIN")
 
- 			.build()
 
- 		return MapReactiveUserDetailsService(rob, admin)
 
- 	}
 
- }
 
- ----
 
- ====
 
- You can find a complete sample in {gh-samples-url}/reactive/webflux/java/method[hellowebflux-method]
 
 
  |