JWT签名验证必须校验exp和iat字段,Go标准库jwt/v5默认不强制校验,需显式调用VerifyExpiresAt和VerifyIssuedAt;网关统一鉴权优于微服务重复实现;禁用panic处理认证失败,应返回标准HTTP状态码;服务间调用推荐mTLS而非JWT;须防范时钟漂移影响exp校验。

JWT签名验证必须校验exp和iat字段
不校验过期时间的JWT验证等于没做认证。Go标准库github.com/golang-jwt/jwt/v5默认不强制校验exp,需显式调用VerifyExpiresAt和VerifyIssuedAt。
常见错误是只调用token.Valid就放行请求,此时即使token已过期也会通过验证。
- 必须在
jwt.Parse的Keyfunc之后,手动调用token.Claims.(jwt.MapClaims).VerifyExpiresAt(time.Now().UTC(), true) - 若使用
jwt.WithValidMethods,需同时传入jwt.WithTimeFunc确保时区一致(微服务跨节点时尤其关键) - 建议把校验逻辑封装为独立函数,避免每个handler重复写
if !claims.VerifyExpiresAt(...)
API网关层统一鉴权比在每个微服务里重复实现更可靠
当服务拆分为user-service、order-service等独立进程后,在每个服务里都写一遍JWT解析+RBAC判断,会带来三类问题:密钥同步难、权限逻辑不一致、无法全局限流/熔断。
推荐用轻量网关(如gin-gonic/gin + gorilla/mux)前置处理,把认证结果以context.Context透传给下游服务。
立即学习“go语言免费学习笔记(深入)”;
- 网关解析
Authorization: Bearer,验证通过后注入ctx = context.WithValue(ctx, "user_id", uid) - 下游服务直接从
ctx.Value("user_id")取值,不再碰Authorization头 - 若需细粒度权限(如“只能删自己订单”),由业务服务查DB判断,网关只管身份真实性
http.HandlerFunc中间件中别用panic处理认证失败
用panic拦截未授权请求看似简洁,但会导致HTTP连接异常关闭、日志无状态、Prometheus指标丢失,且与Go的error-first惯例冲突。
正确做法是返回标准HTTP状态码,并确保响应体格式统一(如所有微服务都返回{"code":401,"msg":"invalid token"})。
- 认证失败时调用
w.WriteHeader(http.StatusUnauthorized),再json.NewEncoder(w).Encode(map[string]interface{}{"code": 401, "msg": "invalid token"}) - 避免在中间件里
return后继续执行handler——用if err != nil { return }结构明确中断流程 - 若用
chi或echo框架,优先使用其内置的Abort()或Return()机制,而非裸写http.Error
服务间调用要用mTLS而非Bearer Token
用户端用JWT没问题,但order-service调user-service这类内部调用,若仍传JWT,会暴露用户token、增加签名开销、且无法绑定具体服务实例。
mTLS(双向TLS)让每个服务持有唯一证书,由TLS层完成身份识别,应用层完全无感知。
- 用
crypto/tls配置http.Client时,必须设置TLSClientConfig的RootCAs(CA证书)和Certificates(服务证书+私钥) - 服务启动时校验证书是否过期:
cert.Leaf.NotAfter.Before(time.Now().Add(7*24*time.Hour)) - Kubernetes环境下,用
cert-manager自动签发证书,避免硬编码路径;本地开发可用step-ca快速搭建测试CA
exp校验依赖各节点系统时间一致。K8s集群里务必启用chrony或ntpd同步,否则网关认为token有效而下游服务判定已过期。










