仅靠c.Cookie("token")做限流不可靠,因Cookie由客户端控制,可被伪造、篡改或重放;必须结合服务端签名验证、时效校验与Redis状态计数,才能实现安全有效的限流。

直接用 Cookie 做限流不可靠,必须配合服务端存储 + 签名校验,否则前端可伪造、篡改、重放。
为什么不能只靠 c.Cookie("token") 做限流判断
Cookie 是客户端可控的,c.Cookie() 读出来的值未经验证,攻击者可手动构造请求头或修改浏览器 Cookie 绕过限制。常见错误包括:
- 仅检查
cookie.Value != ""就放行 - 把
cookie.MaxAge当作限流窗口,忽略服务端时间漂移和并发写入冲突 - 未对 Cookie 值做签名或加密,导致 token 可被解码、篡改后重复使用
真正有效的限流必须满足:可验证来源(签名)、有时效性(服务端控制)、有状态(服务端计数)。
用 signed cookie + Redis 实现每用户每小时 10 次请求
核心思路是:生成带签名和过期时间的 Cookie,服务端解析时校验签名与时间,并在 Redis 中维护该用户的计数器。
- 签名密钥必须保密且长度 ≥ 32 字节,用
crypto/rand.Read()生成,不要硬编码 - Cookie 的
Value存的是 base64 编码后的userID|timestamp|signature,不是明文 ID - 每次请求先调用
c.Cookie("user_token"),再用securecookie.Decode()(推荐 github.com/gorilla/securecookie)校验并解包 - 解包成功后,提取
userID,拼成 Redis key 如rate:limit:user:abc123:202611(按小时分片),用INCR+EXPIRE原子操作 - 若
INCR返回值 > 10,立即返回http.StatusTooManyRequests
示例关键片段:
func rateLimitByCookie(next echo.Handler) echo.Handler {
return echo.HandlerFunc(func(c echo.Context) error {
cookie, err := c.Cookie("user_token")
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, "missing or invalid token")
}
var payload struct {
UserID string `json:"uid"`
Timestamp int64 `json:"ts"`
}
if err := secureCookie.Decode("user-token", cookie.Value, &payload); err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, "invalid signature")
}
if time.Now().Unix()-payload.Timestamp > 3600 {
return echo.NewHTTPError(http.StatusUnauthorized, "token expired")
}
key := fmt.Sprintf("rate:limit:user:%s:%s", payload.UserID, time.Now().Format("200601"))
count, err := rdb.Incr(ctx, key).Result()
if err != nil {
return err
}
if count == 1 {
rdb.Expire(ctx, key, 3600*time.Second)
}
if count > 10 {
return echo.NewHTTPError(http.StatusTooManyRequests, "too many requests")
}
return next.ServeHTTP(c.Response(), c.Request())
})
}
Cookie 设置细节决定限流是否生效
限流 Cookie 不是普通会话 Cookie,它需要更严格的传输与存储控制:
-
MaxAge和Expires必须设为短周期(如 3600),且两者只设其一;同时设可能被浏览器忽略 -
Secure必须为true(仅 HTTPS 传输),否则中间人可劫持 token -
HttpOnly必须为true,防止 XSS 注入后 JS 读取并滥用 -
SameSite推荐Lax或Strict,避免 CSRF 场景下被第三方站点携带 - Domain 字段要显式设置(如
.example.com),否则子域名间无法共享,导致限流不一致
设置时务必用 c.SetCookie() 传入完整 *http.Cookie 实例,禁用 http.SetCookie() —— 后者绕过 Echo 中间件链,可能被后续日志或压缩中间件覆盖 Header。
容易被忽略的边界问题
真实部署中,以下三点最容易出问题:
- Redis 连接超时或失败时,限流逻辑应降级为“放行”而非 panic 或阻塞,否则整个服务不可用
- 用户注销或换设备后,旧 Cookie 仍可能在浏览器里残留,需配合服务端主动失效机制(如黑名单 key
rate:blocked:uid) - 同一用户多标签页并发请求,Redis
INCR虽原子,但若限流窗口内刚好卡在第 10 次和第 11 次之间,可能漏判——需加GETSET或 Lua 脚本保证精确性
限流不是加个中间件就完事,它本质是服务端状态 + 客户端凭证 + 时间窗口的三方协同,任何一环松动都会让策略失效。











