Cookie防篡改不能仅靠HttpOnly和Secure,因二者不防重放与伪造;必须结合HMAC-SHA256签名(value|timestamp|nonce)、合理SameSite/MaxAge策略及服务端时效校验,密钥须环境变量加载且≥32字节。

Cookie 防篡改不是给 Set-Cookie 加个 HttpOnly 就完事——它必须结合签名、时效、作用域三者协同,否则攻击者用 Burp 抓包改个 user_id=1001 就能越权。
为什么不能只依赖 HttpOnly 和 Secure?
HttpOnly 阻止 XSS 读取 Cookie,Secure 强制 HTTPS 传输,但两者都不防“合法用户被诱导点击恶意链接后,浏览器自动带上已登录 Cookie 发起请求”这种场景。防篡改核心是让 Cookie 值本身不可伪造、不可重放。
常见错误现象:
- 前端 JS 拼接 token 存入 Cookie,服务端只校验存在性 → 攻击者伪造任意字符串通过
- 用时间戳做有效期但没绑定 session ID → 同一时间戳 + 签名可被多个用户复用
- 签名密钥写死在代码里(如
"my-secret")→ 一旦源码泄露,全量 Cookie 失效
如何用 HMAC-SHA256 签名 Cookie 值
签名不是加密,目标是检测篡改。推荐对 value|timestamp|random 三元组做签名,再拼成完整 Cookie 值。
实操建议:
- 生成时:用
crypto/hmac计算hmac.New(sha256.New, secretKey),输入为fmt.Sprintf("%s|%d|%s", rawValue, time.Now().Unix(), nonce),其中nonce是 16 字节随机 hex(hex.EncodeToString(randBytes)) - 存储时:Cookie 值格式为
base64(rawValue) + "." + base64(signature),避免特殊字符干扰解析 - 校验时:先按
.拆分,base64 解码两段;重新计算签名,用hmac.Equal()比较(绝不用==) - 密钥加载:从环境变量读取
os.Getenv("COOKIE_SIGNING_KEY"),启动时校验长度 ≥32 字节,否则 panic
怎么安全地设置 SameSite 和 MaxAge
SameSite 不是开关,而是策略选择;MaxAge 不是越长越好,得和签名时效对齐。
使用场景与参数差异:
- 敏感操作(如支付确认页):设
SameSite=Strict+MaxAge=300(5 分钟),签名 timestamp 容忍窗口也设为 ±300 秒 - 普通登录态:用
SameSite=Lax+MaxAge=86400(24 小时),但签名中必须含session_id字段,防止 token 被跨账号复用 - 绝对不要设
SameSite=None且不带Secure=true—— 浏览器会直接拒收 - 别信
Expires:现代浏览器以MaxAge为准,Expires仅作兼容 fallback
中间件里怎么统一校验并注入 context
校验必须在所有业务 handler 执行前完成,且失败要立即终止,不能留空 context 或默认值。
关键点:
- 用
e.Use()注册全局中间件时,排除健康检查路径:if c.Request().URL.Path == "/health" { c.Next(); return } - 从
c.Cookie("auth_token")读值,解析失败或签名不匹配时,调用c.AbortWithStatus(http.StatusUnauthorized)并返回空响应体(微信/支付宝回调要求无 body) - 校验成功后,把解出的
user_id、expires_at写入c.Set("user_id", uid),后续 handler 用c.Get("user_id")取,别重复解析 - 务必检查
time.Now().Unix() > expiresAt,别只信 Cookie 里的 timestamp 字段——它可能被客户端篡改后仍通过签名(因签名含该字段)
最易被忽略的是签名原文中是否包含客户端 IP 或 User-Agent——看似增强安全性,实则导致 CDN 缓存穿透、移动端网络切换时频繁失效。防篡改的底线是“值不可伪造”,不是“值不可预测”。


















