Armeria集成gRPC Java如何实现拦截器并正确操作Context
Armeria 集成 gRPC 实现JWT鉴权的Context操作实现方案
核心实现逻辑
你采用装饰器模式包裹gRPC服务做鉴权的思路是可行的,Armeria框架下不推荐直接操作原生gRPC的Context对象,优先使用框架自带的ServiceRequestContext做请求维度的上下文存储,该对象和请求生命周期完全绑定,自动支持线程切换、异步任务的上下文传播,不会出现上下文丢失问题,适配所有Armeria生态组件。
分步实现
1. 定义上下文属性Key
首先定义全局唯一的属性Key,用于在Context中存取鉴权后的数据,避免和框架内置属性、其他自定义属性冲突:
public final class AuthAttrKeys { // 泛型指定你要存储的鉴权结果类型,比如用户信息、权限列表等 public static final AttributeKey<UserAuthInfo> USER_AUTH = AttributeKey.valueOf(AuthAttrKeys.class, "USER_AUTH_INFO"); }
2. 实现JWT鉴权装饰器
自定义装饰器实现请求拦截、JWT校验、上下文写入、请求放行的全逻辑:
public class JwtAuthDecorator implements DecoratingHttpServiceFunction { private final JwtTokenValidator tokenValidator; // 自行实现的JWT校验工具 @Override public HttpResponse serve(HttpService delegate, ServiceRequestContext ctx, HttpRequest req) throws Exception { // 从请求头获取Authorization字段 final String authHeader = req.headers().get(HttpHeaderNames.AUTHORIZATION); if (authHeader == null || !authHeader.startsWith("Bearer ")) { // 校验不通过直接返回gRPC标准未鉴权错误 return GrpcStatus.UNAUTHENTICATED .withDescription("缺失或非法的Authorization请求头") .asHttpResponse(); } final String token = authHeader.substring(7); final UserAuthInfo authInfo; try { // 执行JWT合法性、有效期、权限范围校验 authInfo = tokenValidator.validate(token); } catch (JwtValidateException e) { return GrpcStatus.UNAUTHENTICATED .withDescription("Token校验失败: " + e.getMessage()) .asHttpResponse(); } // 校验通过,将鉴权结果写入当前请求的Context ctx.setAttr(AuthAttrKeys.USER_AUTH, authInfo); // 放行请求到下游实际gRPC服务 return delegate.serve(ctx, req); } }
3. 注册装饰器到gRPC服务
构建gRPC服务时挂载你实现的鉴权装饰器,无需修改现有业务gRPC服务的代码:
GrpcService businessGrpcService = GrpcService.builder() .addService(new UserServiceImpl(), new OrderServiceImpl()) // 你的业务gRPC服务实现 // 挂载鉴权装饰器,所有挂载的gRPC接口都会经过鉴权逻辑 .decorate(JwtAuthDecorator.class, () -> new JwtAuthDecorator(jwtTokenValidator)) .build(); // 将gRPC服务绑定到Armeria Server即可 Server server = Server.builder() .http(8080) .service(businessGrpcService) .build();
4. 业务逻辑中读取上下文数据
在任意gRPC业务方法中,不需要手动传参,直接获取当前请求绑定的Context即可读取之前写入的鉴权数据:
@Override public void getUserInfo(GetUserInfoReq request, StreamObserver<GetUserInfoResp> responseObserver) { // 直接获取当前请求上下文,同步/异步场景都可安全调用 ServiceRequestContext ctx = ServiceRequestContext.current(); UserAuthInfo authInfo = ctx.attr(AuthAttrKeys.USER_AUTH); // 直接使用authInfo做后续业务处理,比如鉴权判断、获取用户ID等 Long userId = authInfo.getUserId(); // ... 业务逻辑 }
注意事项
- 禁止混用原生gRPC Context作为主存储:Armeria虽然兼容原生gRPC Context的读写,但原生Context需要手动处理异步场景的传播,容易出现上下文丢失问题,仅当你需要兼容依赖原生gRPC Context的老组件时才考虑使用
- 无需手动做上下文的attach/detach操作:Armeria会自动将
ServiceRequestContext传播到当前请求派生的所有线程、CompletableFuture、虚拟线程等异步执行单元,直接调用ServiceRequestContext.current()即可获取 - 如果你需要对部分gRPC方法跳过鉴权,可以在装饰器中通过
ctx.rpcRequest().method()获取当前调用的gRPC方法全限定名,配置白名单放行即可
内容的提问来源于stack exchange,提问作者Chan Rao
相关产品推荐
相关产品推荐

