Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using ReactiveSecurityContextHolder inside a Kotlin Flow

I'm working on a Spring Boot (2.2) project using Kotlin, with CouchDB as (reactive) database, and in consequence, async DAO (either suspend functions, or functions returning a Flow). I'm trying to setup WebFlux in order to have async controllers too (again, I want to return Flows, not Flux). But I'm having troubles retrieving my security context from ReactiveSecurityContextHolder.

From what I've read, unlike SecurityContextHolder which is using ThreadLocal to store it, ReactiveSecurityContextHolder relies on the fact that Spring, while making a subscription to my reactive chain, also stored that context inside this chain, thus allowing me to call ReactiveSecurityContextHolder.getContext() from within the chain.

The problem is that I have to transform my Mono<SecurityContext> into a Flow at some point, which makes me loose my SecurityContext. So my question is: is there a way to have a Spring Boot controller returning a Flow while retrieving the security context from ReactiveSecurityContextHolder inside my logic? Basically, after simplification, it should look like this:

@GetMapping
fun getArticles(): Flow<String> {
    return ReactiveSecurityContextHolder.getContext().flux().asFlow() // returns nothing
}

Note that if I return the Flux directly (skipping the .asFlow()), or add a .single() or .toList() in the end (hence using a suspend fun), then it works fine and my security context is returned, but again that's not what I want. I guess the solution would be to transfer the context from the Flux (initial reactive chain from ReactiveSecurityContextHolder) to the Flow, but it doesn't seem to be done by default.

Edit: here is a sample project showcasing the problem: https://github.com/Simon3/webflux-kotlin-sample

like image 267
SimonH Avatar asked Nov 26 '19 11:11

SimonH


1 Answers

What you really try to achieve is accessing your ReactorContext from inside a Flow.

One way to do this is to relax the need for returning a Flow and return a Flux instead. This allows you to recover the ReactorContext and pass it to the Flow you are going to use to generate your data.

@ExperimentalCoroutinesApi
@GetMapping("/flow")
fun flow(): Flux<Map<String, String>> = Mono.subscriberContext().flatMapMany { reactorCtx ->
    flow {
        val ctx = coroutineContext[ReactorContext.Key]?.context?.get<Mono<SecurityContext>>(SecurityContext::class.java)?.asFlow()?.single()
        emit(mapOf("user" to ((ctx?.authentication?.principal as? User)?.username ?: "<NONE>")))
    }.flowOn(reactorCtx.asCoroutineContext()).asFlux()
}

In the case when you need to access the ReactorContext from a suspend method, you can simply get it back from the coroutineContext with no further artifice:

@ExperimentalCoroutinesApi
@GetMapping("/suspend")
suspend fun suspend(): Map<String,String> {
    val ctx = coroutineContext[ReactorContext.Key]?.context?.get<Mono<SecurityContext>>(SecurityContext::class.java)?.asFlow()?.single()
    return mapOf("user" to ((ctx?.authentication?.principal as? User)?.username ?: "<NONE>"))
}
like image 109
aduchate Avatar answered Oct 13 '22 21:10

aduchate