fiber默认不防重放,因无内置时间戳、nonce、signature校验;需手动注册中间件,协同校验三者并处理body复用、redis超时等边界问题。

为什么标准Fiber中间件不直接防重放
Fiber本身没有内置的重放防护机制,fiber.New() 创建的实例默认不校验请求时间戳或 nonce。重放攻击依赖的是“合法但过期的请求被重复提交”,而 Fiber 的 ctx.Body()、ctx.Query() 等方法只管解析,不管时效性——这需要你显式介入请求生命周期。
必须校验的三个字段:timestamp、nonce、signature
防重放不是加个时间窗口就行,关键靠三者协同:
-
timestamp:客户端生成的 Unix 时间戳(单位秒),服务端允许误差 ≤ 300 秒(5 分钟) -
nonce:一次性的随机字符串(如 UUID v4),需在 Redis 中缓存 5 分钟并做 SETNX 去重 -
signature:用服务端密钥对timestamp + nonce + body做 HMAC-SHA256,客户端和服务端各自计算比对
漏掉任意一项都可能被绕过:只校 timestamp 可重放;只校 nonce 不防时序重放;不校 signature 则整个机制形同虚设。
在Fiber中注册校验中间件的正确姿势
不要用 app.Use("/api", middleware) 这种路径前缀方式——它会跳过 /api/v2/order 这类嵌套路由。应使用 app.Use(middleware) 全局注册,并在中间件内用 ctx.Path() 和 ctx.Method() 显式过滤目标接口(如仅 POST /api/pay)。
示例核心逻辑:
func ReplayProtection() fiber.Handler {
return func(c *fiber.Ctx) error {
ts := c.Get("X-Timestamp")
nonce := c.Get("X-Nonce")
sig := c.Get("X-Signature")
// 1. 校验 timestamp 格式和范围
if ts == "" || !isValidTimestamp(ts) {
return c.Status(fiber.StatusBadRequest).SendString("invalid timestamp")
}
// 2. 检查 nonce 是否已存在(Redis SETNX)
key := "replay:" + nonce
if ok, _ := redisClient.SetNX(c.Context(), key, "1", 5*time.Minute).Result(); !ok {
return c.Status(fiber.StatusForbidden).SendString("replay detected")
}
// 3. 重新计算 signature 并比对
body, _ := c.Body()
expected := sign(ts + nonce + string(body))
if !hmac.Equal([]byte(sig), []byte(expected)) {
return c.Status(fiber.StatusUnauthorized).SendString("invalid signature")
}
return c.Next()
}
}
容易被忽略的边界问题
实际部署时这几个点经常出错:
- 客户端传
X-Timestamp是毫秒还是秒?Fiber 默认不自动转换,服务端必须统一按秒解析,否则 1725500000000 会被当成非法值 - Body 读取后
c.Body()会变空,后续 handler 拿不到原始数据——要用c.Request().Body()配合bytes.NewReader重置,或提前用c.Request().CopyBody() - Redis 连接超时未处理,会导致校验永远失败;建议加 fallback 逻辑(如本地 LRU cache + 降级告警)
- GET 请求没 body,但 query 参数也要参与签名,否则
?amount=100¤cy=USD可被截获重放
最麻烦的是签名原文拼接顺序:不同语言 SDK 容易把 timestamp 放最后,而 Go 这边写成 nonce + ts + body,结果永远对不上。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











