123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- [[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`.
- ====
- [tabs]
- ======
- 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:
- [tabs]
- ======
- 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.
- [tabs]
- ======
- 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:
- [tabs]
- ======
- 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:
- [tabs]
- ======
- 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/configuration/webflux.adoc#jc-webflux[WebFlux Security], the Reactor Context is automatically established by Spring Security according to the authenticated user.
- [tabs]
- ======
- 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]
|