You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
6.2 KiB
236 lines
6.2 KiB
[[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].
|
|
|