# HTTP的Cookie机制
30 秒速记
Cookie= 服务器让浏览器替它记的小纸条,给无状态的HTTP补上「记性」- 流程:响应
Set-Cookie下发 → 浏览器存起来 → 之后匹配的请求自动带Cookie头 - 属性三类:有效期
Expires/Max-Age,作用域Domain/Path,安全HttpOnly/Secure/SameSite HttpOnly防JS读、Secure只走HTTPS、SameSite管跨站带不带;它们都不加密Cookie本身- 用途:登录会话(最常见)、广告追踪(第三方
Cookie,各家浏览器在收紧)
Cookie 就是服务器托浏览器保管的一张小纸条,之后每次请求浏览器都自动带上,服务器就能认出你是谁。 HTTP 本身不记事,第一次来服务器用 Set-Cookie 发一张纸条,浏览器存下来,后面发请求自动塞进 Cookie 头。纸条在浏览器手里,所以要靠属性约束它:多久过期、发给哪些域名和路径、JS 能不能读、只能走 HTTPS、跨站时带不带。实际项目里 Cookie 一般只放一个随机的 sessionId,用户信息存在服务端,别把敏感数据直接写进去。
面试官追问
追问 1用户换了个浏览器就得重新登录,原来的浏览器还登着,说明服务端会话被删了吗?
不说明。Cookie 是存在每个浏览器自己的存储里的,新浏览器没有那张纸条,服务端自然认不出。原浏览器还能用,恰好说明服务端会话还在。
追问 2登录接口响应里有 Set-Cookie,是不是就能确定登录成功了?
不一定。还要看浏览器有没有存下来,DevTools 的 Application 面板里能看到;再看下一个请求有没有带上。跨域请求没加 credentials: 'include',或者 SameSite=None 没配 Secure,都会被浏览器直接丢掉。
追问 3登录凭证能被 document.cookie 读到,加个 Secure 能解决吗?
不能。Secure 只限制走 HTTPS 发送,跟 JS 能不能读无关。要禁止脚本读取得加 HttpOnly,这样 XSS 注入的脚本拿不到会话凭证。
追问 4想让手机和电脑共享登录状态,直接把 Cookie 复制过去行不行?
技术上能用,但这就是会话劫持的做法,泄露一次别人就能冒充你。正经做法是扫码登录:手机确认后服务端给电脑单独签一个新会话,两边各自持有、可以分别撤销。
# Cookie 的工作过程
30 秒速记
- 第一次访问:服务器在响应里放
Set-Cookie: sid=abc,浏览器存下来 - 之后请求:浏览器自动把匹配的
Cookie放进请求头,服务器拿sid认人 - 多个值:响应里可以有多个
Set-Cookie头,请求里合成一行Cookie: a=1; b=2 Cookie存在浏览器里,换浏览器、换设备、开无痕都不共享- 服务器通常只把
Cookie当索引,用sid去服务端会话里查用户,别把权限直接写在Cookie里
Cookie 的工作过程就是一来一回:服务器用 Set-Cookie 发纸条,浏览器在之后的请求里用 Cookie 头把纸条交回去。 下发的时候一个 Set-Cookie 头只能写一个键值,要发多个就写多个头;浏览器回传时不分开写,全部用分号拼在一个 Cookie 头里。这张纸条是浏览器在管,不是操作系统,所以换个浏览器服务器就不认识你了。实际开发中服务器一般只发一个随机的会话 ID,真正的用户信息放在服务端,Cookie 只是钥匙。
当用户通过浏览器第一次访问服务器的时候,服务器肯定是不知道他的身份的。所以,就要创建一个独特的身份标识数据,格式是“key=value”,然后放进 Set-Cookie 字段里,随着响应报文一同发给浏览器。
浏览器收到响应报文,看到里面有 Set-Cookie,知道这是服务器给的身份标识,于是就保存起来,下次再请求的时候就自动把这个值放进 Cookie 字段里发给服务器。
因为第二次请求里面有了 Cookie 字段,服务器就知道这个用户不是新人,之前来过,就可以拿出 Cookie 里的值,识别出用户的身份,然后提供个性化的服务。
不过因为服务器的“记忆能力”实在是太差,一张小纸条经常不够用。所以,服务器有时会在响应头里添加多个 Set-Cookie,存储多个“key=value”。但浏览器这边发送时不需要用多个 Cookie 字段,只要在一行里用“;”隔开就行

从这张图中我们也能够看到,Cookie 是由浏览器负责存储的,而不是操作系统。所以,它是“浏览器绑定”的,只能在本浏览器内生效。
如果你换个浏览器或者换台电脑,新的浏览器里没有服务器对应的 Cookie,就好像是脱掉了贴着纸条的衣服,“健忘”的服务器也就认不出来了,只能再走一遍 Set-Cookie 流程
原理拆解: Cookie 循环包含下发、保存、匹配和回传四步。服务器用 Set-Cookie 分别创建记录;浏览器按来源及属性保存,并在后续请求的主机、路径、安全条件匹配时生成 Cookie 请求头。服务器通常只把其中的随机会话标识当作索引,再到会话存储中查询用户资料,而不是把可信权限直接交给客户端决定。
最小验证: 运行以下 Node.js 程序,再访问 http://localhost:3000/。输入是连续两次浏览器请求,要验证第一次响应下发 sid,刷新后浏览器自动回传。
const http = require('node:http');
http.createServer((req, res) => {
const cookies = Object.fromEntries(
(req.headers.cookie || '').split('; ')
.filter(Boolean)
.map(item => {
const i = item.indexOf('=');
return [item.slice(0, i), item.slice(i + 1)];
})
);
if (!cookies.sid) {
res.setHeader('Set-Cookie', [
'sid=a8f31; Path=/; HttpOnly; SameSite=Lax',
'theme=dark; Path=/; SameSite=Lax'
]);
}
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end(cookies.sid ? `recognized:${cookies.sid}` : 'first visit');
}).listen(3000, () => console.log('http://localhost:3000/'));
首次页面显示 first visit,刷新后预期显示 recognized:a8f31。数组中的两个值会生成两个 Set-Cookie 响应字段;浏览器回传时通常形成类似 sid=a8f31; theme=dark 的请求头。解析时以第一个 = 为分界,避免值中存在 = 时被错误截断。
边界与排查: 浏览器只发送满足作用域和策略的记录,因此“已经保存”不保证每个请求都携带。隐私模式、清除站点数据、更换浏览器或设备都会打断关联;禁用或拦截 Cookie 也会让示例持续停留在首次访问。可在开发者工具的 Network 中分别检查响应的 Set-Cookie 和下一次请求的 Cookie,再到站点存储面板核对域、路径、有效期及阻止原因。服务端还应把未知、过期或伪造的会话标识视为未登录。
面试官追问
追问 1服务端要同时下发 sid 和 theme 两个 Cookie,能写在一个 Set-Cookie 里用逗号隔开吗?
不能。一个 Set-Cookie 只对应一个 Cookie,日期里本身就有逗号,合并会解析错。Node 里写 res.setHeader('Set-Cookie', ['sid=abc; HttpOnly', 'theme=dark']),传数组会生成两个头。
追问 2Cookie 里能直接放 role=admin 来判断权限吗?
不能,Cookie 在用户手里,改个值就提权了。要么只放随机 sid,权限去服务端查;要么用签名的 token,服务端验签,但也只是防篡改,敏感信息照样别放。
追问 3Set-Cookie 明明返回了,下一个请求却没带上,按什么顺序查?
先去 Application 面板看有没有存下来,没存的话 Network 里那条响应的 Cookies 标签会有黄色感叹号说明被拦截的原因;存了没带,就对照请求的域名、路径、协议、是否跨站和过期时间逐个比。
追问 4Cookie 每个请求都带,体积有讲究吗?
有。单个 Cookie 上限大约 4KB,而且同域所有请求都会带,图片、脚本也不例外。一个域名下塞了 3KB 的 Cookie,页面 100 个请求就白传 300KB,所以静态资源常放到不带 Cookie 的独立域名上。
# Cookie 的属性
30 秒速记
- 有效期:
Expires是绝对时间,Max-Age是秒数,同时存在以Max-Age为准;都不写就是会话Cookie,关浏览器就没 - 删除
Cookie:同名、同Domain、同Path再下发一次,Max-Age=0 - 作用域:
Domain不写只发给当前主机,写了会连子域名一起发;Path按前缀匹配
