Skip to content

SecurityContext not propagated to gRPC executor in servlet mode — @PreAuthorize fails #387

@nixel2007

Description

@nixel2007

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:

  1. SecurityContextServerInterceptor (captures SecurityContext from SecurityContextHolder)
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions