先记住这个答案
HttpOnly 阻止 JavaScript 通过 document.cookie 访问 Cookie,使 XSS 注入的脚本偷不走会话凭证。Secure 让 Cookie 只在 HTTPS 加密请求中发送,防止明文 HTTP 链路上的中间人窃听。SameSite 规定 Cookie 是否随跨站请求发送:Strict 完全禁止跨站携带,Lax 只允许顶级导航的 GET 请求携带,None 允许跨站但必须配合 Secure,主要缓解 CSRF 和第三方追踪。三者分工不同,防御的是攻击链上不同环节。
- HttpOnly 防脚本窃取会话
- Secure 防明文链路上的窃听
- SameSite 控制跨站携带防 CSRF
三个属性分别切断的攻击链路
攻击者拿到会话 Cookie 通常有三条路:注入脚本读取、在明文链路上嗅探、诱导浏览器自动携带发起伪造请求。HttpOnly 切断第一条:设置后脚本无法通过 document.cookie 读取或修改该 Cookie,因此 XSS 攻击者即使执行任意脚本也拿不到凭证。它缓解的是凭证泄露,并不阻止 XSS 本身,攻击者仍可在受害页内直接发同源请求。
Secure 切断第二条:浏览器只在加密请求中发送该 Cookie,http:// 页面(除 localhost 等特例外)不会携带,中间人无法在明文流量里截获会话。注意 Secure 不是加密 Cookie 内容,磁盘上的值仍是明文,拿到本地文件或配合脚本仍可读取。SameSite 切断第三条:它按请求的发起站点决定是否附带 Cookie,跨站表单提交或第三方图片请求在 Lax 和 Strict 下不携带会话,伪造请求因此失效。
function shouldSend(cookie, requestIsSameSite, method, isTopNav) {
if (cookie.secure === false && cookie.sameSite === 'None') return 'rejected';
if (requestIsSameSite) return 'send';
if (cookie.sameSite === 'Strict') return 'withhold';
if (cookie.sameSite === 'Lax') {
return (method === 'GET' && isTopNav) ? 'send' : 'withhold';
}
return 'send';
}
const lax = { secure: true, sameSite: 'Lax' };
const strict = { secure: true, sameSite: 'Strict' };
const none = { secure: true, sameSite: 'None' };
console.log(shouldSend(lax, true, 'POST', false));
console.log(shouldSend(lax, false, 'POST', false));
console.log(shouldSend(lax, false, 'GET', true));
console.log(shouldSend(strict, false, 'GET', true));
console.log(shouldSend(none, false, 'POST', false));
console.log(shouldSend({ secure: false, sameSite: 'None' }, false, 'POST', false));查看输出与解释
send
withhold
send
withhold
send
rejected纯函数模拟浏览器对 SameSite 三个取值的核心判定逻辑,不依赖 DOM 或网络,可直接运行验证输出。
电商登录态 Cookie 的实际配置决策
某电商站会话 Cookie 名为 sid,登录后用 HTTPS 下发,前端既有页面内 AJAX 读自己的接口,也支持从邮件营销页跳回购物车。决策是:sid 必须 HttpOnly,因为前端没有任何需要读它的逻辑;必须 Secure,因为全站已强制 HTTPS;SameSite 选 Lax 而非 Strict,因为邮件链接跳回属于跨站顶级导航 GET,Lax 下仍携带 Cookie,用户落地即处于登录态。
Strict 曾被考虑但否决:用户从邮件或搜索结果点进来时浏览器不携带 sid,会显示未登录,购物车页强制跳登录,转化率受损。None 也不需要,因为没有第三方 iframe 嵌入本站的需求。假设进行渗透测试,预期注入的脚本读不到 sid,跨站 POST 表单伪造下单请求时浏览器不携带 Cookie,服务端收到匿名请求直接拒绝。三者叠加才同时堵住窃取与伪造两条路径。
属性在什么条件下会失效
HttpOnly 的常见失效场景是服务端同时提供 document.cookie 可读的副本:有人把用户 ID 另存一个非 HttpOnly 的 Cookie 供前端展示,攻击者改偷那个或利用 XSS 直接代发请求。它还挡不住攻击者在受害页内以受害者身份发同源请求,所以 XSS 修复仍是根本。Secure 的边界是:只要站点任一子域或旧入口仍走 HTTP,浏览器可能在加密前泄露其他未加 Secure 的 Cookie,且本地磁盘读取不受它保护。
SameSite 的失效边界更隐蔽:Lax 允许跨站顶级导航 GET 携带 Cookie,若站点把改状态操作放在 GET 接口上,CSRF 依然成立;判断依据是所有变更接口必须走 POST 并校验。另一个边界是新旧浏览器差异,未写 SameSite 的旧客户端可能按 None 处理,跨站仍携带。对应处理是显式声明 SameSite 并在服务端用 CSRF Token 做第二道防线,代价是每次表单渲染要注入并校验 Token。
容易答错的地方
- 以为配了 HttpOnly 就免疫 XSS
- HttpOnly 只让脚本读不到 Cookie,XSS 攻击者仍可在受害页面内向同源接口直接发请求,请求会自动携带 Cookie。它缓解凭证外泄,不能替代输出编码等 XSS 根治手段。
- 以为 Secure 会加密 Cookie 内容
- Secure 只是限定浏览器只在 HTTPS 请求中发送该 Cookie,磁盘存储和服务端收到的值都是明文。要保护内容机密性需在服务端加密或使用不透明会话标识符。
面试官还会怎么问?
SameSite 不设值时浏览器怎么处理?
现代浏览器默认按 Lax 处理,即跨站子资源请求(如图片、脚本、表单提交)不携带,跨站顶级导航 GET 携带。但旧浏览器可能按 None 处理,安全敏感站点应显式声明,避免依赖默认行为差异。
为什么 SameSite=None 必须配合 Secure?
None 表示允许第三方站点请求携带 Cookie,若不限制在加密通道传输,跨站发送的凭证会在明文链路上暴露。浏览器直接拒绝 SameSite=None 且未加 Secure 的 Cookie。
会话 Cookie 三个属性的推荐组合是什么?
通常是 HttpOnly + Secure + SameSite=Lax,服务端用不透明随机会话 ID。若需第三方嵌入则用 SameSite=None; Secure,并配合服务端 CSRF Token 作为纵深防御。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。