会话是【柜子】,会话 ID 是【钥匙】——柜子在服务器那边,钥匙在浏览器这边,两者合起来才把无状态的 HTTP 变成一次有头有尾的交谈。
2-13 讲了 Cookie 怎么把凭证带来带去;这一条讲凭证的另一头:那串 ID 指向的东西存在哪、怎么用、什么时候该换。两条合起来,就是把无状态变成有记性的完整办法。它也是本章的收官。
一、一次交谈要有头有尾
会话指的是一次从头到尾的连续交谈。比如【登录 → 浏览商品 → 加购物车 → 下单 → 付款】,这一整串属于同一个会话。
麻烦在于 HTTP 无状态,每条请求都是新的。怎么把这几步串成一条线?
办法是给这条线起个名字——会话 ID。浏览器每次带着这个名字来,服务器就知道这几条请求说的是同一件事。
二、钥匙与柜子
会话 ID 是钥匙:一小段随机字符串,放在 Cookie 里来回带。会话是柜子:内容在服务器那边——你是谁、买了什么、走到哪一步了。
钥匙上什么都不写,所以钥匙被人看到也读不出信息;但钥匙被复制走,他就能开你的柜子——这就是会话劫持。
于是有了另一派做法:把信息直接写进钥匙里,这就是 JWT(JSON Web Token)。两种做法各有各的账。
三、动手 Demo:两条路的账,摊在十万次请求上
sid, jwt = 100, 850
print("每次请求多带:会话 %d 字节 vs 令牌 %d 字节" % (sid, jwt))
print("10 万次多传:%.1f MB vs %.1f MB"
% (sid * 1e5 / 1048576.0, jwt * 1e5 / 1048576.0))
print("服务器开销:0.5 毫秒 x 10 万 = %.0f 秒 vs 0.05 毫秒 x 10 万 = %.0f 秒"
% (0.5 * 1e5 / 1000.0, 0.05 * 1e5 / 1000.0))
实跑输出:
--------------------------------------------------------------------
D14 系列处理流程的管理:会话、会话 ID
--------------------------------------------------------------------
会话 = 一次【从头到尾的连续交谈】。
比如【登录 -> 浏览商品 -> 加购物车 -> 下单 -> 付款】,这是同一个会话。
麻烦在于 HTTP 无状态,每条请求都是新的。怎么把这几步串成一条线?
办法是给这条线起个名字——会话 ID。
两者的分工,用一个比方最清楚:
会话 ID 是【钥匙】:一小段随机字符串,放在 Cookie 里来回带;
会话是【柜子】:内容存在服务器那边——你是谁、买了什么、到哪一步了。
钥匙上什么都不写,所以钥匙被人看到也读不出信息;
但钥匙被复制走,他就能开你的柜子——这就是会话劫持。
于是有了另一派做法:把信息直接写进钥匙里,这就是 JWT。
----------------------------------------------------------------------------------------
对比项 会话 ID(存服务器) JWT(信息在令牌里)
----------------------------------------------------------------------------------------
存在哪 服务器的存储里 令牌自己身上
每次请求 一次存储查询 一次签名校验
大小 约 32 字节 约 300~800 字节
能撤销吗 删掉记录立刻生效 难,得等它过期
跨服务 要共享同一个存储 各服务拿公钥自己验
----------------------------------------------------------------------------------------
把两条路的开销摊在 10 万次请求上看:
----------------------------------------------------------------------------------------
项目 会话 ID JWT
----------------------------------------------------------------------------------------
每次请求多带的字节 约 100 约 850
10 万次多传的数据 约 9.5 MB 约 81.1 MB
每次请求的服务器开销 约 0.5 毫秒 约 0.05 毫秒
10 万次的服务器开销 约 50 秒 约 5 秒
----------------------------------------------------------------------------------------
两笔账正好相反:JWT 省服务器、费带宽;会话省带宽、费服务器。
但真正的分水岭不是这两个数字,是【能不能立刻撤销】:
用户点了退出、账号被盗、权限被收回——
会话这条路:删一行记录,下一秒所有人都不认这把钥匙;
令牌这条路:要么等它自己过期(常见 15 分钟),
要么再建一张【已撤销名单】——等于把刚省下的那份状态又加了回来。
所以 2026 年最常见的做法是两条路各取一半:
浏览器这边用会话 Cookie(HttpOnly + Secure + SameSite=Lax);
服务器内部几个服务之间互调,用短期的令牌。
最后是四条不该省的规矩:
----------------------------------------------------------------------------------------
规矩 为什么
----------------------------------------------------------------------------------------
会话 ID 至少 128 位随机数 太短会被猜,猜到一次就等于登录成功
登录成功后必须换一个新 ID 防会话固定攻击(攻击者预先埋一个 ID 给你)
带上 HttpOnly 与 Secure 脚本读不到、明文拿不到
设置过期时间(闲置 + 绝对) 让被遗忘的会话自己关掉
----------------------------------------------------------------------------------------
一句话收尾:Cookie 是【怎么把钥匙带来带去】,
会话是【这把钥匙能开哪个柜子】——两者合起来,
才把无状态的 HTTP 变成了一次有头有尾的交谈。
读这个输出:两条路的账正好相反——会话省带宽、费服务器;令牌省服务器、费带宽。注意这两个数字都还不算大,真正让人做决定的不是它们,而是下面这件事:撤销。
四、真正的分水岭:能不能立刻撤销
用户点了退出、账号被盗、权限被收回——这三种情况都要求【立刻不认】。
会话这条路:删掉那一行记录,下一秒所有人都不认这把钥匙了。令牌这条路:要么等它自己过期(常见 15 分钟),要么再建一张【已撤销名单】——等于把刚省下的那份状态又加了回来。
所以 2026 年最常见的做法是两条路各取一半:浏览器这边用会话 Cookie(HttpOnly + Secure + SameSite),服务器内部几个服务之间互调时用短期的令牌。用户浏览器里从来没有令牌,令牌只在服务之间流动。
五、四条不该省的规矩
第一,会话 ID 至少要 128 位随机数——太短会被猜,而猜到一次就等于登录成功。
第二,登录成功后必须换一个新的会话 ID——这是防【会话固定攻击】:攻击者预先埋一个他知道的 ID 给你用,等你登录成功,那个 ID 就变成了你的钥匙。
第三,Cookie 要带上 HttpOnly 与 Secure——脚本读不到、明文拿不到。第四,设置过期时间(闲置多久自动失效 + 最长活多久),让被遗忘的会话自己关掉。
一句话收尾:Cookie 是【怎么把钥匙带来带去】,会话是【这把钥匙能开哪个柜子】——两者合起来,才把无状态的 HTTP 变成了一次有头有尾的交谈。这也是第二章从【无状态】一路讲到这里的原因。
30 秒实验
- 登录一个你需要账号的网站,然后按 F12 打开 Application 面板,在 Cookies 里找到那条会话。
- 看它的属性:HttpOnly 勾了吗?Secure 勾了吗?过期时间是【会话结束时】还是一个具体时间?
- 点退出登录,再回来看——那条 Cookie 还在不在?值变了吗?
常见误区
- 错误:会话就是 Cookie,两个词说的是一回事。——纠正:它们是两个层面。Cookie 是浏览器这一侧的传输机制,会话是服务器这一侧的存储。一个负责把钥匙带来带去,一个负责保管柜子里的东西——教材把它们分成相邻两条,正是为了拆开这一点。
- 错误:JWT 比会话先进,所以应该优先用它。——纠正:取舍不在新旧,在【要不要立刻撤销】。单体的、面向浏览器的应用,会话更简单也更安全;多服务、跨域、移动端,令牌更合适。2026 年最常见的做法是两者混用,各管一段。
- 错误:登录验证通过就完事了。——纠正:登录成功后必须换一个新的会话 ID。否则就给了【会话固定攻击】可乘之机——攻击者先把一个自己知道的 ID 塞给你,你登录之后,那条 ID 就自动升级成了你的登录凭证。这一步不做,前面所有密码校验都白费。
- 错误:会话 ID 是随机生成的,猜不到。——纠正:随机不等于够长。熵不够的随机数可以被暴力枚举,而猜中一次就等于登录成功一次。所以这里有一条明确的下限:至少 128 位。
自测三问(点开对答案)
1. 为什么说【会话是柜子、会话 ID 是钥匙】? —— 因为**信息存在服务器那边,浏览器手里只有一串没有含义的编号**。这个分工带来两个后果:钥匙被看到也读不出内容(安全),但钥匙被复制走就能开柜子(所以要做 HttpOnly、要能立刻撤销)。**理解了这一层,后面所有的安全措施都有地方挂。** 2. 为什么【能不能立刻撤销】比带宽和服务器开销更重要? —— 因为**带宽和开销是常态成本,撤销能力是事故成本**。用户点了退出、账号被盗,这些场景要求【下一秒就不认】。**常态成本可以优化,事故成本只能靠设计提前留好**——所以绝大多数面向浏览器的应用会选会话。 3. 第二章十四条,如果只记住一句话,记哪句? —— 记 **【HTTP 不记人,所以要在它外面加上一层记性】。** 这一章从 SoR 与 SoE 的分工讲起,经过 HTML、CSS、静态与动态、请求与响应、开发者工具、CGI、前后端分离、两边的脚本语言,最后落在 Cookie 与会话——**整条线都在回答同一个问题:一个天生不留记忆的协议,怎么让一连串操作看起来像一次完整的交谈。**- ☐ 下一章(3-1 为整个Web提供支持的技术:与 Web 服务器相关的服务器)
相关笔记
- 支持重新连接的机制 —— 上一节:钥匙怎么带来带去
- Web网站的HTTP请求 —— 无状态这个设计的起点
- Web技术的发展 —— 本章开篇:为什么要变
- 为整个Web提供支持的技术 —— 下一章开篇:不直接露面却在下面撑着 Web 的那些技术
- 创建Web服务器 —— 下一章最后一节:从下单到能打开页面
- 🛡️ 网络与信息安全 —— 会话劫持、跨站伪造这些攻击的完整讲法
- Cookie、会话 —— 编程课里的同一组概念
- 🕸️ Web技术原理 · 索引