必须校验timestamp+nonce,仅靠jwt的exp字段无法防御秒级重放攻击;需在payload中强制包含iat和nonce,网关层验证iat时效性(如≤90s)、nonce未重复(redis setnx+ttl),并剥离原始jwt签发短期service_token透传。

防重放必须校验 timestamp + nonce,不能只靠 exp
JWT 的 exp 字段只能防止过期重放,对秒级内重复请求完全无效。真实攻击场景中,攻击者截获一次有效请求后,在 exp 到期前反复重放——这在支付、转账等接口上极其危险。
正确做法是:在 JWT payload 中强制包含 iat(签发时间)和 nonce(一次性随机字符串),网关层校验时需同时满足:
-
iat距当前时间不超过 60 秒(可配置,建议 ≤90s,容忍 NTP 偏移) -
nonce在 Redis 中未出现过(用 SETNX + TTL 实现,TTL 设为 120s) - 仍需调用
token.VerifyExpiresAt(time.Now(), true),不能跳过
示例校验逻辑片段:
claims := jwt.MapClaims{}
token, _ := jwt.ParseWithClaims(tokenStr, &claims, keyFunc)
if !token.Valid {
return false
}
if !claims.VerifyIssuedAt(time.Now(), true) || !claims.VerifyExpiresAt(time.Now(), true) {
return false
}
nonce, ok := claims["nonce"].(string)
if !ok || len(nonce) == 0 {
return false
}
exists, _ := redisClient.SetNX(ctx, "nonce:"+nonce, "1", 120*time.Second).Result()
if !exists {
return false // 已存在,重放
}
网关层必须剥离原始 Token,签发 service_token 再透传
直接把用户原始 JWT 透传给下游服务,等于把密钥、角色、权限全暴露给内部网络——一旦某个服务被攻破,攻击者可伪造任意用户身份调用其他服务。
安全做法是:网关验证用户 JWT 后,立即丢弃原始 token,用私钥生成短期 service_token,仅含必要字段:
-
caller_service(如"api-gateway") -
target_service(从路由规则中提取,如"order-service") -
user_id(只透传 ID,不带 role/permissions) -
ttl(严格设为 5 分钟,避免长生命周期泄露)
下游服务只认这个 service_token,且必须用网关统一分发的公钥验签。别在下游再解析原始 JWT。
Redis 黑名单与 nonce 缓存必须分库隔离
共用一个 Redis DB 是常见隐患:redis-cli flushdb 会同时清空黑名单和 nonce 缓存,导致已登出用户 token 突然复活,或重放防护失效。
生产环境必须按用途拆库:
- DB 0:JWT 黑名单(
jti→ TTL,用于登出/强制下线) - DB 1:nonce 缓存(
nonce:xxx→ TTL,仅用于防重放) - DB 2:角色权限缓存(
role:admin→ Hash,TTL 600s)
Go 连接时显式指定 DB:
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
DB: 1, // 防重放专用库
})
Kong Go 插件里禁止手动解析 Authorization 头
在 Kong 中写 Go 插件时,直接调用 r.Header.Get("Authorization") 是错的——Kong 的 Lua 主进程已对 header 做过预处理(如大小写归一、空格清理),裸 HTTP request 对象不可信。
必须通过 PDK 获取:
- 取 token:
pdk.request.get_header("authorization")(注意小写) - 设错误响应:
pdk.response.set_status(401)+pdk.response.set_header("WWW-Authenticate", "Bearer") - 终止流程:
pdk.response.exit(401, []byte(`{"error":"invalid_token"}`)),别用panic或os.Exit
插件编译命令也必须加 CGO_ENABLED=0,否则 Kong 加载失败报 plugin not found or invalid。
iat 和 nonce 必须配合 Redis 原子操作落地,且三者校验缺一不可;更关键的是,网关一旦验完用户 token,就必须立刻“脱敏”再透传,否则整个链路的信任模型就塌了。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











