Closed userquin closed 4 years ago
ok, a first version could be (can be integrated in the project?):
const val SECURED_COMMON_POOL = "SecuredCommonPool"
const val SECURED_UNCONFINED = "SecuredUnconfined"
internal open class SpringSecurityCoroutineContext(
private val dispatcher: ContinuationInterceptor
): AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
private var securityContext: SecurityContext = SecurityContextHolder.getContext()
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = dispatcher.interceptContinuation(Wrapper(continuation))
inner class Wrapper<T>(private val continuation: Continuation<T>): Continuation<T> {
private inline fun wrap(block: () -> Unit) {
try {
SecurityContextHolder.setContext(securityContext)
block()
} finally {
securityContext = SecurityContextHolder.getContext()
SecurityContextHolder.clearContext()
}
}
override val context: CoroutineContext get() = continuation.context
override fun resume(value: T) = wrap { continuation.resume(value) }
override fun resumeWithException(exception: Throwable) = wrap { continuation.resumeWithException(exception) }
}
}
internal open class SpringSecurityCoroutineContextResolver: CoroutineContextResolver {
override fun resolveContext(beanName: String, bean: Any?): CoroutineContext? = when(beanName) {
SECURED_COMMON_POOL -> SpringSecurityCoroutineContext(CommonPool)
SECURED_UNCONFINED -> SpringSecurityCoroutineContext(Unconfined)
else -> bean as? CoroutineContext
}
}
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
open class CoroutineContextResolverConfiguration {
...
..
@Bean
@ConditionalOnClass("org.springframework.security.core.context.SecurityContext")
open fun springSecurityCoroutineContextResolver(): CoroutineContextResolver =
SpringSecurityCoroutineContextResolver()
}
Previous code will not work just because the context is cached by CoroutineMethodInterceptor, so all calls will use the same SpringSecurityCoroutineContext.
val contextKey = coroutine.context to coroutine.name
val context = contextMap[contextKey] ?: getContext(contextKey).apply { contextMap[contextKey] = this }
so the solution is to capture SecurityContext in the interceptor:
Just move private var securityContext: SecurityContext = SecurityContextHolder.getContext() from SpringSecurityCoroutineContext to Wrapper.
const val SECURED_COMMON_POOL = "SecuredCommonPool"
const val SECURED_UNCONFINED = "SecuredUnconfined"
internal open class SpringSecurityCoroutineContext(
private val dispatcher: ContinuationInterceptor
): AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = dispatcher.interceptContinuation(Wrapper(continuation))
inner class Wrapper<T>(private val continuation: Continuation<T>): Continuation<T> {
private var securityContext: SecurityContext = SecurityContextHolder.getContext()
private inline fun wrap(block: () -> Unit) {
try {
SecurityContextHolder.setContext(securityContext)
block()
} finally {
securityContext = SecurityContextHolder.getContext()
SecurityContextHolder.clearContext()
}
}
override val context: CoroutineContext get() = continuation.context
override fun resume(value: T) = wrap { continuation.resume(value) }
override fun resumeWithException(exception: Throwable) = wrap { continuation.resumeWithException(exception) }
}
}
internal open class SpringSecurityCoroutineContextResolver: CoroutineContextResolver {
override fun resolveContext(beanName: String, bean: Any?): CoroutineContext? = when(beanName) {
SECURED_COMMON_POOL -> SpringSecurityCoroutineContext(CommonPool)
SECURED_UNCONFINED -> SpringSecurityCoroutineContext(Unconfined)
else -> bean as? CoroutineContext
}
}
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
open class CoroutineContextResolverConfiguration {
...
..
@Bean
@ConditionalOnClass("org.springframework.security.core.context.SecurityContext")
open fun springSecurityCoroutineContextResolver(): CoroutineContextResolver =
SpringSecurityCoroutineContextResolver()
}
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL)
How can I access/configure SecurityContext inside suspend function?
In this example, UserService::changePassword method access to SecurityContextHolder.getContext(): it is null when controller method has the suspend modifier (changePasswordNotWorking) while default method is working (changePassword). I suppose it is also applicable when service has security annotations: