Spring服务向gRPC拦截器传递Bearer Token及信息失败排查
问题分析与解决方案
核心问题:Context未正确绑定到调用作用域
你当前代码的问题在于Context.current().withValue()方法会返回一个新的Context实例,但你并没有将这个新Context设置为当前上下文,也没有在这个新Context的作用域内执行gRPC调用。原来的代码只是创建了新Context,但实际执行grpcClient.getEmployee(...)时,仍然使用的是原来的Context,所以拦截器中获取不到值。
正确的实现方式
方式1:使用Context.run()包裹gRPC调用(推荐)
Context.run()会将新Context设置为当前上下文,并在执行完传入的任务后自动恢复原来的Context,无需手动管理:
// spring-service方法修改 public Employee getEmployee() { ... // 创建包含用户信息的新Context Context newContext = Context.current() .withValue(ContextKeyHolder.USER_INFO, currentUser.getUsername()) .withValue(ContextKeyHolder.BEARER, currentUser.getBearerToken()); // 在新Context的作用域内执行gRPC调用 return newContext.run(() -> grpcClient.getEmployee(...)); }
方式2:手动attach/detach Context
如果需要更细粒度的控制,可以手动绑定和恢复Context:
// spring-service方法修改 public Employee getEmployee() { ... Context oldContext = Context.current(); Context newContext = oldContext .withValue(ContextKeyHolder.USER_INFO, currentUser.getUsername()) .withValue(ContextKeyHolder.BEARER, currentUser.getBearerToken()); // 将新Context设置为当前上下文 Context attachedContext = newContext.attach(); try { return grpcClient.getEmployee(...); } finally { // 恢复原来的Context attachedContext.detach(oldContext); } }
关于ThreadLocal的选择
不推荐使用ThreadLocal,原因如下:
- Spring GraphQL的DataFetcher可能会在异步线程池中执行,线程会发生切换,ThreadLocal存储的值无法跨线程传递,导致拦截器中获取不到数据。
- gRPC的
Context本身就是为了解决跨线程、异步场景下的上下文传递设计的,它会自动跟随调用栈(包括异步回调)传递,比ThreadLocal更适配gRPC和Spring GraphQL的异步执行模型。
拦截器补充优化
确保在拦截器中正确获取值并设置到gRPC请求头:
@Override public void start(Listener<RespT> responseListener, Metadata headers) { String userInfo = ContextKeyHolder.USER_INFO.get(Context.current()); String bearerToken = ContextKeyHolder.BEARER.get(Context.current()); if (userInfo != null) { headers.put(Metadata.Key.of("user-info", Metadata.ASCII_STRING_MARSHALLER), userInfo); } if (bearerToken != null) { headers.put(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER), "Bearer " + bearerToken); } super.start(responseListener, headers); }
内容的提问来源于stack exchange,提问作者Govinda Sakhare
相关产品推荐
相关产品推荐

