Go微服务中需在grpc.NewServer()时通过grpc.UnaryInterceptor和grpc.StreamInterceptor显式传入拦截器函数,二者分别处理一元和流式RPC;必须类型签名正确、返回err中断或handler放行,Metadata取值须判空,多拦截器需链式组合且注意执行顺序。

Go微服务里怎么给gRPC方法加统一拦截器
gRPC本身不提供中间件机制,但grpc.UnaryInterceptor和grpc.StreamInterceptor是标准入口。必须在grpc.NewServer()时显式传入,否则拦截器完全不生效。
常见错误是把拦截器函数定义成普通函数却忘了返回err,导致校验失败后请求仍继续执行。正确写法是:拦截逻辑结束后,要么调用handler(ctx, req)放行,要么直接return nil, err中断。
- Unary拦截器只对一元RPC(如
GetUser)有效,流式方法(如WatchEvents)必须单独配StreamInterceptor - 拦截器函数签名固定:
func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error),少一个参数就会编译报错 - 别在拦截器里做耗时操作(比如查DB),否则所有请求都会卡住;高频权限校验建议用本地缓存或预加载的
map[string]bool
权限校验拦截器里如何安全提取用户身份
gRPC Metadata是唯一可靠的上下文载体,HTTP Header里的Authorization不会自动透传到gRPC。客户端必须显式用metadata.Pairs("authorization", "Bearer xxx")带上,服务端再用metadata.FromIncomingContext(ctx)取。
容易踩的坑是直接md["authorization"]取值——这会panic,因为map可能不含该key。必须先if val, ok := md["authorization"]; ok判断。
- JWT解析推荐用
github.com/golang-jwt/jwt/v5,注意验证exp和iss,别只校验签名 - 权限字段(如
role或permissions)应放在JWT payload里,而不是靠查库动态加载,否则每次请求都打DB - 拒绝请求时返回
status.Errorf(codes.PermissionDenied, "no access"),不要用codes.Unauthenticated混淆认证与授权
日志拦截器为什么总打不出请求参数和耗时
gRPC拦截器里req是interface{},直接fmt.Sprintf("%v", req)会输出类似&{0xc0001a2b40}的地址,不是实际数据。必须类型断言或用protojson.Marshal序列化。
耗时统计不能只靠time.Since(start),因为handler()可能panic,导致defer不执行。稳妥做法是用recover()兜底。
- 日志结构体字段建议固定:method、status、duration_ms、user_id(从JWT取)、trace_id(从ctx取)
- 敏感字段如密码、token要过滤,可用
strings.Contains(strings.ToLower(field), "token")粗筛,但生产环境建议白名单字段打印 - 别用
log.Printf,优先接入结构化日志库(如go.uber.org/zap),避免日志解析失败
多个拦截器怎么组合才不互相干扰
gRPC只允许注册一个UnaryInterceptor,所以多个逻辑(比如鉴权+日志+限流)必须手动链式调用。典型写法是把每个拦截器封装成函数,然后用chain.UnaryServerInterceptor(auth, log, rateLimit)方式组合。
官方grpc.ChainUnaryInterceptor是可行方案,但要注意顺序:鉴权必须在日志之前,否则未授权请求也会被记日志;限流应在鉴权之后,避免攻击者绕过鉴权直接打爆限流器。
- 自己实现链式调用时,每个拦截器的
handler参数其实是下一个拦截器的入口,最后一个才真正调用业务方法 - 如果某个拦截器需要修改
req(如解密请求体),必须返回新req并传给下一个handler,原req不可变 - 调试时可在每个拦截器开头打一行
zap.Debug("enter auth interceptor"),确认执行顺序是否符合预期
拦截器看似简单,但权限校验的边界、日志的字段粒度、多拦截器的执行顺序,三者稍有偏差就导致线上问题难以排查。尤其在跨团队协作时,约定好拦截器的输入输出契约比写代码更重要。











