JWT签发与校验须密钥分离:签发用私钥(RS256),校验用公钥;Refresh Token需绑定设备指纹并存DB;gRPC透传用户上下文应通过metadata而非header;Istio RBAC仅作粗粒度鉴权,业务级权限仍需服务内实现。

JWT 签发与校验必须分离密钥管理
微服务间身份传递依赖 JWT,但很多团队直接在每个服务里硬编码 secret 或复用同一份密钥,导致一处泄露全盘失守。签发(如认证服务)和校验(如订单、用户服务)应使用不同密钥策略:签发端用私钥签名(RS256),校验端只持公钥;或至少用不同 secret 并定期轮换。
常见错误是校验时仍调用 jwt.Parse(token, func(t *jwt.Token) (interface{}, error) { return []byte("same-secret"), nil }) —— 这让所有服务都掌握签名密钥。
- 认证服务用
rsa.PrivateKey调用token.SignedString() - 其他服务加载
rsa.PublicKey传给jwt.ParseWithClaims(..., jwt.Keyfunc) - 公钥建议从配置中心或文件加载,避免编译进二进制
Go-Micro / gRPC 中间件如何透传用户上下文
HTTP 层解析完 JWT 后,用户 ID、角色等信息需安全透传到下游 gRPC 微服务,不能靠 header 原样转发(易被篡改),也不能在每次 RPC 请求体里重复塞字段(冗余且破坏协议契约)。
正确做法是在网关或 API 层将解析后的 claims 注入 context.Context,再通过 gRPC 的 metadata.MD 下发,并在服务端中间件中提取还原:
立即学习“go语言免费学习笔记(深入)”;
// 客户端注入
md := metadata.Pairs("user_id", claims.UserID, "role", claims.Role)
ctx = metadata.NewOutgoingContext(ctx, md)
// 服务端中间件提取
md, ok := metadata.FromIncomingContext(ctx)
if ok {
userID := md.Get("user_id")
// 校验 userID 是否非空、是否为合法 UUID 等
}
- 务必校验
md.Get("user_id")非空且格式合法,不能无条件信任 metadata - 避免透传敏感字段如手机号、邮箱,只传最小必要标识(如
sub或内部user_id) - 若用 Go-Micro,优先使用
micro.WrapHandler封装校验逻辑,而非每个 handler 里重复写
Refresh Token 必须绑定设备指纹与短时效
仅靠 JWT 过期时间(exp)无法支撑长期登录体验,但直接用长时效 refresh token 又极大增加泄露风险。Golang 实现时,refresh token 不该是无状态字符串,而应是数据库可追溯的记录。
典型错误是把 refresh token 当作另一个 JWT 存储并验证,却未绑定客户端特征:
- 生成时存入 DB,字段包括:
token_hash(bcrypt 加盐哈希)、user_id、fingerprint(UA + IP + 设备 ID 拼接后哈希)、expires_at(建议 7 天) - 每次 refresh 请求必须比对当前请求的
fingerprint与 DB 记录一致 - 成功刷新后立即使原 token 失效(
DELETE或设revoked = true) - 绝不返回原始 refresh token,只返回新 token 和新过期时间
Service Mesh 场景下 AuthZ 为何不能只靠 Istio RBAC
当用 Istio 管理微服务流量时,容易误以为开启 AuthorizationPolicy 就完成了权限控制。实际上 Istio RBAC 只能基于请求 header、source.principal 等做粗粒度路由级拦截,无法处理“用户 A 能否编辑文章 B”这类业务级鉴权。
真实场景中,你仍需在服务内部实现细粒度检查:
- Istio 可校验 JWT 是否由可信 issuer 签发(用
requestPrincipal),但不解析 payload - 业务代码中需调用
CheckPermission(ctx, userID, "update:article", articleID),该函数可能查 ACL 表、调用权限中心 API 或读取缓存 - 避免在 HTTP handler 里直连数据库做权限判断,应封装为轻量 service client 异步调用
最常被忽略的是:Istio 的 AuthorizationPolicy 默认允许未匹配规则的请求,必须显式设置 action: DENY 并配 default deny 规则,否则等于没开。










