Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

spring security context not propagated #19

Closed
userquin opened this issue Mar 16, 2018 · 3 comments
Closed

spring security context not propagated #19

userquin opened this issue Mar 16, 2018 · 3 comments

Comments

@userquin
Copy link

userquin commented Mar 16, 2018

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:

@RestController
@Coroutine(COMMON_POOL)
class UserController(
  val userService: UserService
) {
  @PostMapping(
            "/api/change-password-working",
            consumes = [MediaType.APPLICATION_JSON_UTF8_VALUE],
            produces = [MediaType.APPLICATION_JSON_UTF8_VALUE]
  )
  fun changePassword(@RequestBody body: MutableMap<String, Any?>): SomeObject = userService.changePassword(body)
  @PostMapping(
            "/api/change-password-not-working",
            consumes = [MediaType.APPLICATION_JSON_UTF8_VALUE],
            produces = [MediaType.APPLICATION_JSON_UTF8_VALUE]
  )
  suspend fun changePasswordNotWorking(@RequestBody body: MutableMap<String, Any?>): SomeObject = userService.changePassword(body)
}
@userquin
Copy link
Author

userquin commented Mar 16, 2018

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()
}

@userquin
Copy link
Author

userquin commented Mar 18, 2018

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()
}

@dndll
Copy link

dndll commented Nov 15, 2019

SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants