grpc-ecosystem / grpc-spring

Spring Boot starter module for gRPC framework.
https://grpc-ecosystem.github.io/grpc-spring/
Apache License 2.0
3.47k stars 812 forks source link

An Authentication object was not found in the SecurityContext #628

Open TheHett opened 2 years ago

TheHett commented 2 years ago

Hi. I trying to use annotation @security checks, my config:

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, proxyTargetClass = true)
class Configuration {
    @Bean
    fun grpcAuthenticationReader(projectRepository: ProjectRepository): GrpcAuthenticationReader {
        return ApiTokenAuthenticationReader(projectRepository)
    }

    @GrpcGlobalServerInterceptor
    fun securityContextCoroutineContextServerInterceptor(): ServerInterceptor {
        return object : CoroutineContextServerInterceptor() {
             override fun coroutineContext(call: ServerCall<*, *>, headers: Metadata): CoroutineContext {
                 return Dispatchers.Default + SecurityCoroutineContext()
             }
         }
    }
}

Also added SecurityCoroutineContext from here https://github.com/yidongnan/grpc-spring-boot-starter/issues/462#issuecomment-921140929

Service

@GrpcService
class FileService() : FileServiceGrpcKt.FileServiceCoroutineImplBase() {
    @Secured("ROLE_PROJECT")
    override suspend fun getInfo(request: FileInfoRequest): FileInfoResponse {
        ....
    }
}

But error occurs

An Authentication object was not found in the SecurityContext

The method ApiTokenAuthenticationReader.readAuthentication was not called, therefore I suspect that problem not in my reader.

The question

  1. what did i miss?
  2. it's possible to get Principal inside a rpc method?

Stacktraces and logs

org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:336) ~[spring-security-core-5.6.1.jar:5.6.1]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:200) ~[spring-security-core-5.6.1.jar:5.6.1]
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:58) ~[spring-security-core-5.6.1.jar:5.6.1]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.14.jar:5.3.14]
    at biz.m.f.grpc.FileService$$EnhancerBySpringCGLIB$$9b66f240.getInfo(<generated>) ~[281ddc99-6f03-48c8-ac53-fe4834233627/:na]
    at f.api.FileServiceGrpcKt$FileServiceCoroutineImplBase$bindService$1.invoke(FileServiceOuterClassGrpcKt.kt:153) ~[281ddc99-6f03-48c8-ac53-fe4834233627/:na]
    at f.api.FileServiceGrpcKt$FileServiceCoroutineImplBase$bindService$1.invoke(FileServiceOuterClassGrpcKt.kt:153) ~[281ddc99-6f03-48c8-ac53-fe4834233627/:na]
    at io.grpc.kotlin.ServerCalls$unaryServerMethodDefinition$2$$special$$inlined$map$1$2.emit(Collect.kt:139) ~[grpc-kotlin-stub-1.2.0.jar:na]
    at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15) ~[kotlinx-coroutines-core-jvm-1.5.2.jar:na]
    at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15) ~[kotlinx-coroutines-core-jvm-1.5.2.jar:na]
    at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:77) ~[kotlinx-coroutines-core-jvm-1.5.2.jar:na]
    at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:59) ~[kotlinx-coroutines-core-jvm-1.5.2.jar:na]
    at io.grpc.kotlin.HelpersKt$singleOrStatusFlow$1$invokeSuspend$$inlined$collect$1.emit(Collect.kt:139) ~[grpc-kotlin-stub-1.2.0.jar:na]
    at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15) ~[kotlinx-coroutines-core-jvm-1.5.2.jar:na]
    at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15) ~[kotlinx-coroutines-core-jvm-1.5.2.jar:na]
    at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:77) ~[kotlinx-coroutines-core-jvm-1.5.2.jar:na]
    at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:59) ~[kotlinx-coroutines-core-jvm-1.5.2.jar:na]
    at io.grpc.kotlin.ServerCalls$serverCallListener$requests$1.invokeSuspend(ServerCalls.kt:227) ~[grpc-kotlin-stub-1.2.0.jar:na]
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) ~[kotlin-stdlib-1.6.10.jar:1.6.10-release-923(1.6.10)]
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) ~[kotlinx-coroutines-core-jvm-1.5.2.jar:na]
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) ~[kotlinx-coroutines-core-jvm-1.5.2.jar:na]
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750) ~[kotlinx-coroutines-core-jvm-1.5.2.jar:na]
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) ~[kotlinx-coroutines-core-jvm-1.5.2.jar:na]
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665) ~[kotlinx-coroutines-core-jvm-1.5.2.jar:na]

The application's environment

ST-DDT commented 2 years ago

Currently these aren't implemented yet (only non reactive).

You have to convert this class to reactive for it to work https://github.com/yidongnan/grpc-spring-boot-starter/blob/master/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java

It would be nice, if you could contribute it once you have implemented it.

TheHett commented 2 years ago

Hm, I'm sorry, it working now. I just didn't implement AuthenticationManager. My knowlege of spring is poor.

Context may be accessed like this, it works too.

val auth: Authentication = SecurityContextHolder.getContext().authentication
TheHett commented 2 years ago

Maybe it will be better to prevent work without registered AuthenticationManager?

ST-DDT commented 2 years ago

I'm not sure what you are suggesting. Could you please explain your idea in more detail?

TheHett commented 2 years ago

If AuthenticationManager isn't registered in DI container then the application started but we will get the error in runtime:

An Authentication object was not found in the SecurityContext

It possible to check existence AuthenticationManager on boot stage? It's just an idea, maybe not correct.

ST-DDT commented 2 years ago

AFAICT the grpc security parts only gets activated if there is such an AuthenticationManager. https://github.com/yidongnan/grpc-spring-boot-starter/blob/master/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java#L63

I don't see any grpc-spring-boot-starter lines in the stacktrace so this is a spring security/config bug.

TheHett commented 2 years ago

I think it can be closed, since everything works fine? Just one thing that I added:


    @GrpcGlobalServerInterceptor
    fun contextCoroutineInterceptor(): ServerInterceptor {
        return object : CoroutineContextServerInterceptor() {
            override fun coroutineContext(call: ServerCall<*, *>, headers: Metadata): CoroutineContext {
                return Dispatchers.Default + SecurityCoroutineContext()
            }
        }
    }
class SecurityCoroutineContext(
    private val securityContext: SecurityContext = SecurityContextHolder.getContext()
) : ThreadContextElement<SecurityContext?> {

    companion object Key : CoroutineContext.Key<SecurityCoroutineContext>

    override val key: CoroutineContext.Key<SecurityCoroutineContext> get() = Key

    override fun updateThreadContext(context: CoroutineContext): SecurityContext? {
        val previousSecurityContext = SecurityContextHolder.getContext()
        SecurityContextHolder.setContext(securityContext)
        return previousSecurityContext.takeIf { it.authentication != null }
    }

    override fun restoreThreadContext(context: CoroutineContext, oldState: SecurityContext?) {
        if (oldState == null) {
            SecurityContextHolder.clearContext()
        } else {
            SecurityContextHolder.setContext(oldState)
        }
    }
}

I don't know if this should be added to the project?

ST-DDT commented 2 years ago

I don't know if this should be added to the project?

I leave this open until we have decided to either add it, document it or discard it.