-
Notifications
You must be signed in to change notification settings - Fork 80
Description
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(capturesSecurityContextfromSecurityContextHolder)GrpcServerExecutorProvider(wraps an executor inDelegatingSecurityContextExecutor)
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,SecurityContextHolderis populated SecurityContextServerInterceptor.interceptCall()runs on the gRPC executor thread (not the servlet thread), capturing an emptySecurityContextSecurityContextHandlerListener.onHalfClose()restores the empty context@PreAuthorize("isAuthenticated()")fails withAuthenticationCredentialsNotFoundException
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:runIn another terminal:
docker run --network="host" fullstorydev/grpcurl -plaintext \
-H 'Authorization: Basic dXNlcjpwYXNzd29yZA==' \
-d '{"name": "World"}' localhost:8080 com.example.grpcbug.Greeter/SayHelloResult: 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 succeedsSuggested 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
- Anonymous can access gRPC method protected by
PreAuthorizeannotation #245 — similar SecurityContext issue but for native gRPC server mode (fixed in 0.11.0)