Description
In servlet mode (spring-grpc-server-web), @PreAuthorize on @GrpcService methods fails with AuthenticationCredentialsNotFoundException even though the servlet security filter chain successfully authenticates the request.
Root cause
GrpcServletSecurityConfigurerConfiguration creates two beans:
SecurityContextServerInterceptor (captures SecurityContext from SecurityContextHolder)
GrpcServerExecutorProvider (wraps an executor in DelegatingSecurityContextExecutor)
However, GrpcServerFactoryAutoConfiguration.GrpcServletConfiguration#grpcServlet does not inject GrpcServerExecutorProvider. The gRPC servlet uses its own default executor where SecurityContextHolder is empty.
As a result:
- Servlet thread (
nio-8080-exec-N) authenticates via filter chain, SecurityContextHolder is populated
SecurityContextServerInterceptor.interceptCall() runs on the gRPC executor thread (not the servlet thread), capturing an empty SecurityContext
SecurityContextHandlerListener.onHalfClose() restores the empty context
@PreAuthorize("isAuthenticated()") fails with AuthenticationCredentialsNotFoundException
Steps to reproduce
Minimal reproducer: https://github.com/nixel2007/spring-grpc-security-bug-repro
git clone https://github.com/nixel2007/spring-grpc-security-bug-repro
cd spring-grpc-security-bug-repro
mvn clean compile
mvn spring-boot:run
In another terminal:
docker run --network="host" fullstorydev/grpcurl -plaintext \
-H 'Authorization: Basic dXNlcjpwYXNzd29yZA==' \
-d '{"name": "World"}' localhost:8080 com.example.grpcbug.Greeter/SayHello
Result: ERROR — in server logs: AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
Workaround
Setting a DelegatingSecurityContextExecutor on the ServletServerBuilder via ServerBuilderCustomizer fixes the issue:
@Bean
ServerBuilderCustomizer<ServletServerBuilder> fix() {
return builder -> builder.executor(
new DelegatingSecurityContextExecutor(Executors.newCachedThreadPool()));
}
The reproducer includes this workaround, toggled via Spring profile fix:
mvn spring-boot:run -Dspring-boot.run.profiles=fix
# Same grpcurl call now succeeds
Suggested fix
GrpcServletConfiguration should use the GrpcServerExecutorProvider bean (if present) when creating the gRPC servlet, similar to how native server configurations handle the executor.
Additionally, it would be beneficial for the default GrpcServerExecutorProvider to attempt injecting an existing TaskExecutor bean (e.g. applicationTaskExecutor) and wrapping it, rather than creating a standalone DelegatingSecurityContextExecutor(Executors.newCachedThreadPool()). This way,
applications that already configure context propagation on their TaskExecutor (e.g. for tracing, MDC, SecurityContext) would get it working out of the box for gRPC as well.
Environment
- spring-grpc 1.0.2
- Spring Boot 4.0.3
- gRPC 1.77.1
- Java 25
Related
Description
In servlet mode (
spring-grpc-server-web),@PreAuthorizeon@GrpcServicemethods fails withAuthenticationCredentialsNotFoundExceptioneven though the servlet security filter chain successfully authenticates the request.Root cause
GrpcServletSecurityConfigurerConfigurationcreates two beans:SecurityContextServerInterceptor(capturesSecurityContextfromSecurityContextHolder)GrpcServerExecutorProvider(wraps an executor inDelegatingSecurityContextExecutor)However,
GrpcServerFactoryAutoConfiguration.GrpcServletConfiguration#grpcServletdoes not injectGrpcServerExecutorProvider. The gRPC servlet uses its own default executor whereSecurityContextHolderis empty.As a result:
nio-8080-exec-N) authenticates via filter chain,SecurityContextHolderis populatedSecurityContextServerInterceptor.interceptCall()runs on the gRPC executor thread (not the servlet thread), capturing an emptySecurityContextSecurityContextHandlerListener.onHalfClose()restores the empty context@PreAuthorize("isAuthenticated()")fails withAuthenticationCredentialsNotFoundExceptionSteps to reproduce
Minimal reproducer: https://github.com/nixel2007/spring-grpc-security-bug-repro
git clone https://github.com/nixel2007/spring-grpc-security-bug-repro cd spring-grpc-security-bug-repro mvn clean compile mvn spring-boot:runIn another terminal:
Result:
ERROR— in server logs:AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContextWorkaround
Setting a
DelegatingSecurityContextExecutoron theServletServerBuilderviaServerBuilderCustomizerfixes the issue:The reproducer includes this workaround, toggled via Spring profile
fix:mvn spring-boot:run -Dspring-boot.run.profiles=fix # Same grpcurl call now succeedsSuggested fix
GrpcServletConfigurationshould use theGrpcServerExecutorProviderbean (if present) when creating the gRPC servlet, similar to how native server configurations handle the executor.Additionally, it would be beneficial for the default
GrpcServerExecutorProviderto attempt injecting an existingTaskExecutorbean (e.g.applicationTaskExecutor) and wrapping it, rather than creating a standaloneDelegatingSecurityContextExecutor(Executors.newCachedThreadPool()). This way,applications that already configure context propagation on their
TaskExecutor(e.g. for tracing, MDC, SecurityContext) would get it working out of the box for gRPC as well.Environment
Related
PreAuthorizeannotation #245 — similar SecurityContext issue but for native gRPC server mode (fixed in 0.11.0)