HTTP Cookie无处不在。每一个登录会话、购物车和分析工具都依赖它们。然而,大多数开发者只是复制粘贴一个头部,然后就不再深入理解这些属性实际的作用——或者当它们使用错误时会发生什么。 Set-Cookie 本指南涵盖了Cookie的结构、每一个有意义的属性、如何解析Cookie字符串,以及为什么
是你的CSRF防御。如果你希望立即动手实践,可以尝试 SameSite IO Tools Cookie Parser Cookie Builder 或 Cookie的实际外观.
当服务器想要设置一个Cookie时,它会发送一个
响应头: Set-Cookie 浏览器会存储这个信息,并在后续请求中将其发送回来作为:
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400
这就是整个机制。复杂性在于这些属性。
Cookie: sessionId=abc123
Cookie结构:分解字符串
一个Cookie字符串遵循一致的格式:
对是第一个部分。第一个分号之后的所有内容是浏览器的指令——服务器在接收到的
name=value; attribute1; attribute2=attributeValue; ...
这 name=value 头中永远不会看到这些属性。 Cookie 您必须正确设置的安全属性
HttpOnly
防止JavaScript通过
HttpOnly 读取Cookie。这是防范XSS攻击窃取会话令牌的直接防御措施。 document.cookie任何用于用户身份验证的Cookie都应设置
Set-Cookie: sessionId=abc123; HttpOnly
。没有充分的理由不这样做。 HttpOnly表示浏览器仅在HTTPS连接中发送该Cookie。如果没有它,Cookie将以明文形式通过HTTP传输,可能被拦截。在生产环境中,会话Cookie始终需要
安全的
Secure 。在本地开发环境中使用 Secure时,可以省略它——浏览器对localhost会做出例外。 http://localhost控制浏览器在跨站请求中是否包含Cookie。这是防范CSRF攻击的主要防御措施。三个值:
SameSite
SameSite ——Cookie不会在跨站请求中发送。最安全,但用户点击来自邮件的链接时会登出(该Cookie不会在初始导航中发送)。
Strict——Cookie在来自外部站点的顶级导航(GET请求)中发送,但不会在嵌入式跨站请求或跨站POST请求中发送。这是没有显式Lax属性的Cookie的浏览器默认设置。SameSite——Cookie在所有跨站请求中发送。适用于第三方Cookie(OAuth流程、嵌入式组件)。必须与None搭配使用。Secure.
# Third-party / cross-site cookie (e.g., OAuth callback)
Set-Cookie: token=xyz; SameSite=None; Secure
如果发送 SameSite=None 没有 Secure,现代浏览器将完全拒绝该Cookie。对于大多数会话Cookie,使用 SameSite=Lax ——它在安全性和可用登录体验之间取得了平衡。
作用域:域名和路径
Domain
这 Domain 属性指定了哪些主机名会接收该Cookie。
Set-Cookie: user=alice; Domain=example.com
使用 Domain=example.com时,Cookie将发送到 example.com 及其所有子域名(api.example.com, app.example.com)。如果没有 Domain 属性,Cookie仅发送到设置它的精确来源——不会发送到子域名。
常见误解:设置 Domain=example.com 并不会将Cookie限制在仅 不是 。它会扩展作用域以包含子域名。如果希望仅限单个主机,应省略该属性。 example.com限制触发Cookie发送的URL路径。
小路
Path 该Cookie仅随对
Set-Cookie: adminToken=xyz; Path=/admin
及其路径以下的请求一起发送。对 /admin 的请求不会包含它。默认值是 / 或 /api ,表示所有路径。 /Max-Age 与 Expires
两者都控制Cookie过期时间。优先使用
采用绝对日期格式的HTTP日期。这是相对于客户端时钟的,而你无法控制它。 Max-Age.
Expires采用从现在起的秒数:Max-Age表示24小时。相对于服务器的意图,而不是客户端的时钟。Max-Age=86400当两者都存在时,
优先。没有这两个属性的Cookie是 Max-Age 会话Cookie ——它在浏览器关闭时消失。 Cookie属性参考
属性
| 作用 | 阻止JavaScript访问该Cookie | 默认 | 推荐 |
|---|---|---|---|
HttpOnly | 未设置 | 始终设置用于身份验证的Cookie | 仅通过HTTPS传输 |
Secure | 生产环境中始终设置 | 始终设置用于身份验证的Cookie | 控制跨站发送 |
SameSite | 宽松(现代浏览器) | 会话使用宽松;第三方使用无 + 安全 | 设置主机名范围 |
Domain | 仅当前主机 | 除非需要跨子域名访问,否则省略 | 设置路径范围 |
Path | 除非需要隔离管理员令牌,否则保留为 / | / | 过期前的秒数 |
Max-Age | 会话Cookie | 优先于Expires | 绝对过期日期 |
Expires | 使用Max-Age代替 | 优先于Expires | 设置Cookie在代码中的方法 |
Node.js(Express)
Python(FastAPI)
app.post('/login', (req, res) => {
// ... verify credentials ...
res.cookie('sessionId', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 86400 * 1000, // milliseconds in Express
path: '/',
});
res.json({ ok: true });
});
手动解析Cookie头
from fastapi import FastAPI, Response
app = FastAPI()
@app.post("/login")
def login(response: Response):
# ... verify credentials ...
response.set_cookie(
key="sessionId",
value=token,
httponly=True,
secure=True,
samesite="lax",
max_age=86400,
path="/",
)
return {"ok": True}
服务器接收到的
这 Cookie 头中仅包含 name=value 对,以 ; :
Cookie: sessionId=abc123; theme=dark; lang=en
分隔。手动解析时:以 ; (分号加空格)分割,然后以 第一个 = 仅分割。需要了解的一些边缘情况:
- 值可以包含
=(基64字符串常见)——始终以第一个=分割 - Cookie名称是大小写敏感的
- 分隔符周围的空白可能变化——防御性地修剪两边
与其自行编写分割逻辑,不如使用 Cookie Builder 来解码任何 Cookie 头并检查每个值,或使用 IO Tools Cookie Builder 来构造一个具有正确属性的 Set-Cookie 头。
Cookie与CSRF
跨站请求伪造利用了浏览器自动在请求到某个域名时包含Cookie的事实,即使该请求由另一个站点发起。一个位于 evil.com 的恶意页面可以向 bank.com/transfer提交一个表单,如果用户已登录,浏览器会随伪造请求发送其会话Cookie。
SameSite=Lax 阻止了大多数CSRF攻击向量,因为跨站POST请求——典型的攻击模式——不会包含Cookie。 SameSite=Strict 更加彻底,但可能影响用户体验。
CSRF令牌作为纵深防御仍然有效,特别是在高风险操作中,以及当 SameSite=None 是第三方上下文所必需时。这两种防御措施相辅相成。
