如何确保前端Cookie内容没有被篡改?
最近在做用户登录功能时发现,如果直接用document.cookie = "userId=123",怎么防止中间人修改Cookie里的userId呢?
试过给Cookie加了加密,但后来想到就算加密了,攻击者可能还是能改密文导致解密失败。查资料说要验证完整性,但具体该怎么做呢?
看别人用过在Cookie里加签名字段,比如这样:
// 假设后端生成的签名逻辑
const secret = 'mysecret';
const payload = 'userId=123';
const hmac = crypto.createHmac('sha256', secret)
.update(payload)
.digest('hex');
document.cookie = `${payload};signature=${hmac}`;
但这样前端直接暴露了签名算法,攻击者能不能伪造签名?是不是应该完全在服务端处理?
第一步,前端不要自己构造 Cookie,也不要暴露签名逻辑。签名必须由后端生成,前端只负责接收和存储。攻击者如果能拿到你的签名算法和密钥,就完全可以伪造,不管你是加密还是哈希。
第二步,后端在用户登录成功之后,生成一个 payload,比如你提到的 userId=123,然后使用 HMAC-SHA256 对这个 payload 进行签名,签名结果附加在 Cookie 里,比如写成 userId=123;signature=xxxxx。后端代码大概是这样:
这里 HttpOnly 是为了防止 XSS 读取 Cookie,Secure 是保证 Cookie 只通过 HTTPS 传输,SameSite=Strict 可以防止 CSRF 攻击,这些都是基础安全措施。
第三步,前端只需要接收这个 Cookie,不需要也不应该去设置它。前端在发送请求时,Cookie 会自动携带过去。后端每次收到请求,都要重新计算 payload 的签名,然后和 Cookie 中的 signature 比较。如果不一样,说明被篡改了,直接拒绝处理。
后端验证签名的逻辑大概如下:
这样做的原理是:签名只能由拥有密钥的服务端生成和验证,攻击者就算能读到 Cookie 内容(比如通过中间人攻击),也无法伪造合法的签名,因为不知道密钥。而且即使他篡改了 payload,签名也会不匹配。
第四步,关于加密的问题,你担心的是对的。光加密还不够,因为攻击者可能截获密文,然后重放使用,或者篡改后导致解密失败。而使用签名可以保证数据的完整性,也就是 payload 没有被改动。
第五步,如果你需要更进一步的安全性,可以考虑使用 JWT。JWT 本质上也是类似的思路,但结构更规范,支持过期时间、加密签名等功能。但即使使用 JWT,也建议在服务端生成和验证,前端只负责存储和传递。
总结一下:
- 签名必须由服务端生成,密钥不能暴露给前端
- Cookie 要设置 HttpOnly、Secure、SameSite 等属性
- 每次请求都要验证签名是否合法
- 不要让前端处理 Cookie 内容或者签名逻辑
这样就能有效防止 Cookie 被篡改了。如果你还打算支持 Token 刷新、黑名单等功能,可以再引入 Redis 这样的中间存储,但这一步可以先不急。先保证 Cookie 本身的完整性,是最关键的。
最简单的办法是把签名验证完全扔到服务端去处理。流程大概是这样的:
1. 后端生成Cookie的时候,同时计算一个HMAC签名(就像你代码里写的那样),然后把这个签名也塞进Cookie。
2. 每次请求到达后端时,后端重新根据收到的Cookie内容和自己的密钥计算一次签名,然后跟传上来的签名对比。
3. 如果签名对不上,直接丢弃这个Cookie,返回错误。
这样即使攻击者改了Cookie里的内容,他也没法伪造出正确的签名,因为不知道你的密钥。
另外提醒一下,别忘了给Cookie设置
HttpOnly和Secure属性,效率虽然会稍微低一点,但安全性提升很多,特别是防止XSS攻击。至于具体代码,后端可以用类似下面的方式验证签名:
记住,安全的事千万别指望前端来做,交给后端效率更高也更靠谱。