grpc拦截器中jwt认证需在unary/stream两类拦截器中统一实现:从metadata提取bearer token并校验,失败时用status.errorf返回unauthenticated;成功后将claims写入ctx供下游使用;日志拦截器需解析fullmethod获取方法名并手动计时;错误拦截器须recover panic、判空status并映射正确codes;认证拦截器必须置于拦截链首位,且stream与unary需分别配置。

gRPC拦截器里怎么加JWT认证逻辑
认证必须在请求进入业务逻辑前完成,否则会绕过权限校验。拦截器是唯一能统一拦截所有 RPC 调用的入口,UnaryServerInterceptor 和 StreamServerInterceptor 都得覆盖,否则一元调用有认证、流式调用就裸奔。
- 从
ctx提取metadata.MD,用md.Get("authorization")拿 token(注意大小写,gRPC metadata key 默认小写) - 别直接用
strings.Split解析 Bearer token——如果值为空或格式不对,Split会返回长度为 1 的切片,导致 panic;先判空再strings.HasPrefix - 验证失败时,必须用
status.Errorf(codes.Unauthenticated, "...")返回,不能 return nil error 或自定义错误,否则客户端收不到标准UNAUTHENTICATED状态码 - 验证通过后,把用户 ID 或 claims 写回
ctx(用context.WithValue),下游 handler 才能安全读取,别存到全局 map 或闭包变量里
日志拦截器为什么总打不出方法名和耗时
因为 gRPC 的 info.FullMethod 是形如 "/helloworld.Greeter/SayHello" 的完整路径,不是函数名;而耗时测量必须手动起停 timer,拦截器本身不自动计时。
- 用
strings.TrimPrefix(info.FullMethod, "/")得到"helloworld.Greeter/SayHello",再按"/"分割取最后一段才是真实方法名"SayHello" - 耗时必须在拦截器开头
time.Now(),结尾time.Since(start),不能依赖中间 handler 的执行时间——比如 handler 里 sleep 了 5s,但实际网络延迟只有 2ms,你该记的是 2ms,不是 5s - 别在日志里打印原始
req或resp结构体,容易触发无限递归(尤其含 proto.Message 接口时),用proto.MarshalTextString或只打字段摘要 - 生产环境禁用
log.Printf,改用结构化日志库(如 zap)并设置采样率,否则高并发下 I/O 成瓶颈
统一错误处理拦截器踩过的三个坑
统一转错误的核心是让所有 handler 只管业务,不碰 status.Error,但实际落地时类型擦除、panic 捕获、状态码映射最容易出错。
- handler 里
panic("db timeout")不会被拦截器捕获——gRPC 默认不 recover,必须在拦截器里用defer func() { if r := recover(); r != nil { ... } }() - 自定义错误实现了
error接口,但没实现GRPCStatus() *status.Status方法,会导致status.Convert(err)返回空 status,客户端收到Unknown而非你期望的Internal - 不要在拦截器里对所有 error 都转成
codes.Internal——比如数据库唯一约束失败应是codes.AlreadyExists,HTTP 映射时语义才准确 - 注意
status.FromError(err)返回的*status.Status可能为 nil,要判空再取.Code(),否则 panic
Go 拦截器链顺序为什么影响认证结果
拦截器是链式调用,顺序错了,认证就可能被绕过或重复执行。gRPC server 初始化时,拦截器按传入 slice 顺序从左到右注册,但执行时是“外层进、内层出”的洋葱模型。
- 认证拦截器必须放在最外层(slice 第一个位置),否则日志或指标拦截器先执行,业务 handler 已运行,认证就失去意义
- 多个拦截器共享 ctx 时,后注册的拦截器看到的是前一个拦截器修改过的 ctx——比如 A 拦截器往 ctx 写了 user,B 拦截器才能读到;顺序反了就读不到
- 流式拦截器(
StreamServerInterceptor)和一元拦截器(UnaryServerInterceptor)是两套独立链,必须分别配置,漏配任意一种,对应调用就跳过全部拦截逻辑 - 用
grpc.ChainUnaryInterceptor组合多个拦截器时,别手写嵌套调用,容易栈溢出;让它内部做扁平化处理
真正麻烦的是流式拦截器里的 context 生命周期——每次 Recv() 和 Send() 都可能跨 goroutine,认证信息不能只靠一次解析,得在 stream 对象上缓存或重入校验。










