必须启用tls,否则metadata中明文传输的authorization头可被wireshark抓取并重放;grpc不加密metadata,依赖tls保障机密性,生产禁用grpc.withinsecure()。

gRPC 服务端必须启用 TLS 才能安全传递令牌,单纯在 Metadata 里塞 authorization 头而跳过传输加密,等同于裸奔。
为什么 metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer xxx") 在明文连接下完全不安全
HTTP/2 明文(h2c)或未配 grpc.WithTransportCredentials() 的连接,所有 Metadata(包括 authorization)都以明文暴露在网络中。Wireshark 抓包即可见完整 token,攻击者可直接重放调用。
- gRPC 不会自动加密 Metadata —— 它依赖底层 TLS 提供机密性
- 即使服务端写了拦截器校验 JWT,只要传输层没加密,token 就已泄露
-
grpc.WithInsecure()必须只用于本地开发调试,生产环境禁用
UnaryInterceptor 解析 authorization 头的典型写法与坑点
服务端通过拦截器从 metadata.MD 提取并验证 token,但常见错误会让校验形同虚设:
- 没检查
md["authorization"]是否存在或长度是否 ≥2(["Bearer", "xxx"]) - 硬编码用
"Bearer"比较 scheme,但实际可能为"bearer"或"BEARER"—— 应用strings.EqualFold() - JWT 验证后未校验
exp、nbf、iss,或未绑定aud到当前服务名 - 错误地把解析失败的 token 当作匿名请求放行,而非返回
status.Error(codes.Unauthenticated, ...)
示例关键片段:
func authInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok || len(md["authorization"]) == 0 {
return nil, status.Error(codes.Unauthenticated, "missing authorization header")
}
parts := strings.Fields(md["authorization"][0])
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
return nil, status.Error(codes.Unauthenticated, "invalid auth format")
}
tokenStr := parts[1]
claims := jwt.MapClaims{}
_, err := jwt.ParseWithClaims(tokenStr, claims, func(t *jwt.Token) (interface{}, error) {
return []byte("your-secret-key"), nil
})
if err != nil || !claims.VerifyExpiresAt(time.Now().Unix(), true) {
return nil, status.Error(codes.Unauthenticated, "invalid or expired token")
}
return handler(ctx, req)
}
客户端传 token 时,credentials.PerRPCCredentials 比手动塞 metadata 更可靠
手动调用 metadata.AppendToOutgoingContext() 容易漏写、写错 key 名、或在嵌套 context 中丢失。使用 PerRPCCredentials 接口由 gRPC 框架自动注入,更符合设计契约:
- 实现
GetRequestMetadata()返回map[string]string{"authorization": "Bearer " + token} -
RequireTransportSecurity()必须返回true,强制要求 TLS,否则框架会在非安全连接下拒绝调用 - 避免在每个 RPC 前重复构造 context,也规避了
context.WithValue泄露风险
示例:
type tokenAuth struct{ token string }
func (t tokenAuth) GetRequestMetadata(ctx context.Context, uri string) (map[string]string, error) {
return map[string]string{"authorization": "Bearer " + t.token}, nil
}
func (t tokenAuth) RequireTransportSecurity() bool { return true }
// 使用
conn, _ := grpc.Dial("api.example.com:443",
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})),
grpc.WithPerRPCCredentials(tokenAuth{token: "xxx"}),
)
Token 校验后,别忘了做权限映射 —— 认证 ≠ 授权
JWT 解出 sub 或 roles 声明只是第一步。真实业务中,必须将这些声明映射到具体方法的访问策略上:
- 按
info.FullMethod(如"/pb.UserService/UpdateProfile")查白名单或 RBAC 规则 - 若 token 含
"scope": ["user:read", "user:write"],需拆分比对,而非简单字符串包含 - 避免在拦截器里做耗时 DB 查询;可预加载策略缓存,或用 Redis 存 role→perm 映射
- 敏感操作(如删除)应额外要求二次 token(如短期 OTP)或 MFA 声明,不能仅靠主 token
最易被忽略的是:证书认证(mTLS)和 Token 认证是正交的两层。mTLS 保证「谁在连我」,Token 保证「谁被允许调这个接口」——两者缺一不可,且不能互相替代。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











