http.ParseCookie 仅按 RFC 6265 语法解析 Cookie 字符串,不处理 URL 解码、不防篡改、不验证来源,仅作结构化拆分;安全校验必须依赖签名(如 gorilla/securecookie),且验证失败时严禁降级使用原始值。

直接用 http.ParseCookie 解析请求头里的 Cookie 字符串,既不能防篡改,也无法验证来源合法性——它只是按 RFC 6265 做语法拆分,连 URL 解码都不做。真要防篡改,必须签名或加密,且签名验证失败时绝不能回退使用原始值。
为什么 http.ParseCookie 不能用于安全校验
这个函数只负责把 "a=b; Path=/; HttpOnly" 这样的字符串解析成 *http.Cookie 结构体,不碰 Value 字段的内容安全:
- 不自动调用
url.QueryUnescape,若客户端传了user%40example.com,你拿到的就是字面量,不是user@example.com - 对同名多值(如
"theme=dark; theme=light")只取最后一个,且不报错 - 遇到非法分隔符(比如值里有未转义的分号)、空格、等号时直接返回
nil, error,但错误类型是通用http.ErrInvalidCookieName或类似,无法区分是格式错还是被篡改 - 完全不检查
Value是否被客户端修改过——它压根没设计这功能
用 gorilla/securecookie 实现带签名的 Cookie 值
这是目前 Go 生态最成熟、RFC 兼容性最好的防篡改方案。核心是分离「原始数据」和「签名」,靠密钥验证完整性:
- 初始化时必须传两个密钥:
auth key(32 字节 SHA256)和可选的encrypt key(32 字节 AES),少一字节就 panic - 写入前用
s.Encode("auth", map[string]interface{}{"id": "123", "role": "admin"})生成 base64 编码的签名字符串 - 该字符串格式为
value.base64-signature,服务端用s.Decode("auth", raw)验证并反序列化 - 验证失败时,
Decode返回err != nil,此时必须拒绝请求,**不能** fallback 到解析原始Value - 不要把密钥硬编码在代码里;生产环境从环境变量或 secret manager 加载
http.SetCookie 和签名 Cookie 的配合要点
签名只管值,传输安全还得靠 http.Cookie 结构体字段:
立即学习“go语言免费学习笔记(深入)”;
-
Value字段直接填securecookie.Encode返回的字符串,不用再url.QueryEscape——http.SetCookie会自动编码 -
HttpOnly: true必须设,否则 JS 可读可改,签名形同虚设 -
Secure: true生产强制开启;本地开发若用http://localhost,可配开关临时关,但上线前必须切回 -
SameSite: http.SameSiteLaxMode是 CSRF 防御底线,和签名不冲突,但缺一不可 - 删除时仍需构造同名、同
Path、同Domain、同Secure的*http.Cookie,MaxAge: 0,否则浏览器不会清除已签名的那条
手动实现签名时最容易踩的坑
自己用 crypto/hmac 写签名逻辑,90% 的失败源于细节错位:
- 别用
hmac.Sum([]byte(cookieValue + secret))—— 这是长度扩展攻击温床;必须用h := hmac.New(sha256.New, key); h.Write([]byte(value)); sum := h.Sum(nil) - 签名前必须先标准化:比如统一 JSON marshal 的键序、去掉空格、强制小写字段名,否则相同内容因格式差异导致验签失败
- 时间戳字段(如
exp)必须在服务端验证是否过期,不能只信客户端传来的值 - 签名密钥泄露 = 全站 Cookie 可伪造,轮换密钥时要考虑旧 Cookie 的兼容期(比如双密钥验证,逐步下线)
- 若同时用签名和加密,先加密再签名;解密前必须先验签,防止恶意构造密文触发解密异常
真正健壮的 Cookie 防篡改,不是加一层 encode 就完事,而是签名、传输控制、服务端校验三者咬死。最容易被忽略的是:验证失败时的降级行为——返回空、重定向登录页都行,但绝不能把原始未签名值当真用。


















