123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- [[jc-erms]]
- = EnableReactiveMethodSecurity
- Spring Security supports method security by using https://projectreactor.io/docs/core/release/reference/#context[Reactor's Context], which is set up by `ReactiveSecurityContextHolder`.
- The following example shows how to retrieve the currently logged in user's message:
- [NOTE]
- ====
- For this example to work, the return type of the method must be a `org.reactivestreams.Publisher` (that is, a `Mono` or a `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`
- .contextWrite(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`
- .contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication))
- StepVerifier.create(messageByUsername)
- .expectNext("Hi user")
- .verifyComplete()
- ----
- ====
- Where `this::findMessageByUsername` is 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")
- }
- ----
- ====
- The following minimal method security configures 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!")
- }
- }
- ----
- ====
- Alternatively, the following class uses 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')")` ensures that `findByMessage` is invoked only by a user with the `ADMIN` role.
- Note that any of the expressions in standard method security work for `@EnableReactiveMethodSecurity`.
- However, at this time, we support only a return type of `Boolean` or `boolean` of the expression.
- This means that the expression must not block.
- When integrating with xref:reactive/configuration/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].
|