gRPC UnaryInterceptor 权限校验必须返回 status.Error,不能 panic 或裸 return error;metadata 键名须小写;必须显式调用 handler;多个拦截器应使用 go-grpc-middleware.ChainUnaryServer 链式组合。

gRPC UnaryInterceptor 权限校验必须返回 status.Error,不能 panic 或裸 return error
权限拦截失败时,常见错误是直接 panic 或写 return errors.New("xxx"),这会导致 gRPC 返回 codes.Internal 错误,前端无法区分是服务崩溃还是无权访问。正确做法是用 status.Errorf 构造标准错误:
-
status.Errorf(codes.Unauthenticated, "token missing")—— 用于未登录场景 -
status.Errorf(codes.PermissionDenied, "no permission to access /user/delete")—— 用于已登录但权限不足 - 绝对不要在拦截器里调用
log.Fatal、os.Exit或未包装的errors.New
从 metadata.FromIncomingContext 提取 token 时,键名必须小写
gRPC 的 metadata 对 key 做了标准化处理:所有传入的 header key(如 Authorization、X-Api-Key)都会被转为小写后存储。如果你用 md["Authorization"] 去取值,永远拿不到结果。
- 正确写法:
md, ok := metadata.FromIncomingContext(ctx),然后if val, ok := md["authorization"]; ok { ... } - 客户端发送时用大写 key 没问题(
metadata.Pairs("Authorization", "Bearer xxx")),服务端读取时必须用小写 key - 自定义 key 如
tenant-id同样要按小写读取:md["tenant-id"],不是md["Tenant-ID"]
拦截器必须显式调用 handler(ctx, req),否则业务逻辑不执行
这是最隐蔽也最容易踩的坑:拦截器函数签名里有 handler grpc.UnaryHandler 参数,但它不是自动触发的。漏掉这行,请求会静默返回空响应或超时,日志里还看不到任何报错。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 前置校验通过后,必须写
resp, err := handler(ctx, req),且不能放在 defer 里(因为resp和err是 handler 的返回值,defer 执行时还未赋值) - 如果想在 handler 后加日志或 metrics,要写在
handler调用之后,再return resp, err - 即使鉴权失败也要走完 handler 调用 —— 实际上你不会真调它,而是提前
return nil, status.Error(...),但这个return必须在 handler 调用之前(否则业务逻辑就执行了)
多个拦截器要用 go-grpc-middleware.ChainUnaryServer 组合,别手动嵌套
想同时做鉴权、日志、recover,有人会把三个拦截器函数手动套三层,导致嵌套过深、错误传播混乱、panic 捕获失效。正确方式是用社区标准库链式组装。
立即学习“go语言免费学习笔记(深入)”;
- 导入:
"github.com/grpc-ecosystem/go-grpc-middleware" - 组合:
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(authInterceptor, loggingInterceptor, recoverInterceptor)) - 每个拦截器保持单一职责,顺序很重要:鉴权放最前,recover 放最后(否则 panic 会被前面拦截器吞掉)
- 避免自己实现“拦截器套拦截器”的逻辑 ——
ChainUnaryServer已处理好上下文传递、错误透出和 panic 捕获

















