237 lines
6.2 KiB
Plaintext
237 lines
6.2 KiB
Plaintext
[[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`
|
|
.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()
|
|
----
|
|
====
|
|
|
|
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].
|