[[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 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 = 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 findMessageByUsername(String username) { return Mono.just("Hi " + username); } ---- .Kotlin [source,kotlin,role="secondary"] ---- fun findMessageByUsername(username: String): Mono { 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 findMessage() { return Mono.just("Hello World!"); } } ---- .Kotlin [source,kotlin,role="secondary"] ---- @Component class HelloWorldMessageService { @PreAuthorize("hasRole('ADMIN')") fun findMessage(): Mono { 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[], 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]