Iris默认不防重放攻击,因其作为轻量Web框架不内置时间戳校验、nonce缓存或签名验签等业务安全逻辑,仅专注路由与中间件功能,故所有请求只要格式合法、路由匹配即执行handler。

为什么Iris默认不防重放攻击
Iris 是一个轻量、高性能的 Go Web 框架,它本身不内置时间戳校验、nonce 缓存或签名验签逻辑——这些属于业务安全层,不是路由或中间件框架的职责。这意味着:iris.Application 启动后,所有 POST / PUT 接口默认接受任意重复请求,只要参数格式合法、路由匹配,就会一路执行到 handler。你看到的“接口被刷 10 次扣 10 次款”,根本原因就在这里。
在Iris里加时间戳校验(最常用且够用)
核心思路:客户端在请求头(如 X-Timestamp)带毫秒级时间戳,服务端比对是否落在容差窗口内(比如 ±3 分钟)。关键点不是“怎么写 if 判断”,而是如何避免时钟漂移和中间件干扰。
- 必须用
time.Now().UnixMilli()而非time.Now().Unix(),否则秒级精度下,3 分钟窗口实际只剩 1 秒有效(因毫秒部分被截断) - 校验逻辑要放在
iris.Handler中间件里,且必须在ctx.ReadBody()或ctx.ReadJSON()之前执行——否则 body 已读取,后续 handler 再读会失败 - 不要直接用
ctx.GetHeader("X-Timestamp")然后strconv.ParseInt,要加空值和溢出判断,否则ParseIntpanic 会导致整个请求 500 - 示例片段:
func TimestampValidator(tolerance int64) iris.Handler { return func(ctx iris.Context) { tsStr := ctx.GetHeader("X-Timestamp") if tsStr == "" { ctx.StatusCode(400) ctx.WriteString("missing X-Timestamp header") return } ts, err := strconv.ParseInt(tsStr, 10, 64) if err != nil || ts <= 0 { ctx.StatusCode(400) ctx.WriteString("invalid X-Timestamp") return } now := time.Now().UnixMilli() if now-ts > tolerance || ts-now > tolerance { ctx.StatusCode(401) ctx.WriteString("timestamp expired") return } ctx.Next() } }调用:app.Post("/api/pay", TimestampValidator(180_000), payHandler)
加 nonce 防止同一时间戳下的重放(金融/下单场景必选)
仅靠时间戳无法阻止攻击者在 3 分钟窗口内发 100 次相同请求——只要每次 timestamp 都是“当前时间”就行。这时候必须引入 nonce(一次性随机数),服务端需查缓存确认是否已存在。难点不在生成,而在存储和清理。
-
nonce必须是密码学安全的随机字符串,用crypto/rand生成,禁用math/rand - 存储建议用 Redis 的
SET key value EX 180 NX命令:EX 设置过期(与时间戳容差一致),NX 保证原子性,失败即说明已存在 - Iris 中不能在中间件里直接阻塞调用 Redis(会拖慢整个 pipeline),应封装成异步检查 + context 超时控制,或使用带连接池的客户端(如
github.com/go-redis/redis/v9) - 别把
nonce放 query 或 body 里——容易被日志打出来;统一走 header:X-Nonce - 错误处理要明确:Redis 连接失败?返回 503;
SET NX失败?返回 401;超时?也返回 401(不暴露后端状态)
签名 + 时间戳 + nonce 三者缺一不可
只做时间戳或只做 nonce,都可能被绕过。真正安全的组合是:客户端用约定密钥对 timestamp + nonce + 请求 body(或 sorted query)做 HmacSHA256,服务端复现并比对。Iris 本身不提供签名工具,但要注意两个易错点:
- body 读取只能一次。必须在签名验证前用
ctx.Request().Body读取原始字节,再用io.NopCloser重新包装回 request.Body,否则后续ctx.ReadJSON()会读到空 - 签名原文排序必须严格:query 参数按 key 字典序升序拼接,body JSON 字段顺序不保证,所以必须用 canonical JSON(如
github.com/google/json-canonicalize)或约定字段顺序 - 密钥绝不能硬编码在 handler 里,要用
iris.ConfigurationReadOnly或环境变量注入,并确保部署时权限可控
最常被忽略的是:签名验证失败时,仍要走完时间戳和 nonce 校验流程——否则攻击者可通过 signature 错误的响应时间差异,反向探测 nonce 是否已存在。


















