Cookie 是服务端发给客户端、客户端每次自动带回的一小段文本。 它在无状态协议外面补上「你是谁」,代价是每个请求都多带几十字节。
2.3 节说 HTTP 无状态、认不出人。
这一节给出协议层面唯一的补丁:Set-Cookie 与 Cookie 两个首部。第八章讲认证时还会再回到它。
一、一来一回两个首部
服务端在响应里加 Set-Cookie: SID=s01; Path=/; HttpOnly,客户端记住;
之后每次请求都自动加 Cookie: SID=s01。
注意是「每次」——包括图片、CSS、JS 的请求,只要同域同路径。
这就是 Cookie 的固定开销:一个 20 字节的 Cookie,一个页面 100 个请求就是 2 KB。
客户端不需要任何代码就会这么做,这是它比「手写 Token 首部」方便的地方,
也是它容易被滥用(追踪)的原因。
二、三个属性决定它活多久、谁能看
| 属性 | 作用 | 不加的后果 |
|---|---|---|
Expires/Max-Age |
活多久 | 关浏览器就没(会话 Cookie) |
Domain/Path |
发给哪些地址 | 只回发给设置它的域 |
Secure |
只走 HTTPS | 明文链路也会带出去 |
HttpOnly |
JS 读不到 | XSS 能直接偷走 |
SameSite |
跨站要不要带 | 默认宽松,CSRF 风险 |
SameSite 要特别说明:书里没有。它 2016 年才进入标准,2020 年后主流浏览器把默认值改成 Lax。 |
||
| 这是这本书出版后变化最大的一处,读到 Cookie 章节必须补上。 | ||
HttpOnly 与 Secure 是防 XSS 偷 Cookie 的两道墙,但都只是「降低损失」, |
||
| 不能替代输入过滤(11.2.1)。 |
三、实测:不带 Cookie 就是新客人
Demo 里同一个客户端先不带 Cookie 请求,服务端发一个 SID=s01;后两次带上,服务端就认出来了。
另一条连接(同一台机器、同一个进程)不带 Cookie,直接又被当成新客人,拿到 s02。
身份完全由那个字符串决定,跟机器、IP、连接都无关。
这也解释了 Session 劫持(11.4.1):拿到那个字符串,就等于拿到了那个人。
所以它必须走 HTTPS,必须 HttpOnly,最好还要绑定一些辅助特征。
四、动手 Demo:发一个 Cookie,看服务端怎么认人
服务端用 sessions 字典记会话。请求没带 Cookie 就发一个新的,带了就按那个值认人并计数。
def resp(raw, st):
first, hd, body = H.parse_headers(raw)
cookie = hd.get(u'cookie')
hdrs = [(u'Content-Type', u'text/plain')]
if not cookie:
sid = u's%02d' % (len(sessions) + 1)
hdrs.append((u'Set-Cookie', u'SID=%s; Path=/; HttpOnly' % sid))
else:
sid = cookie.split(u'=')[1].split(u';')[0]
sessions[sid] = sessions.get(sid, 0) + 1
实跑输出:
--------------------------------------------------------------------
Cookie:让无状态协议记住一个人
--------------------------------------------------------------------
甲 第 1 次 新客人,发给你 s01
响应带 Set-Cookie SID=s01; Path=/; HttpOnly
甲 第 2 次(带 Cookie) 老客人 s01,第 1 次来
甲 第 3 次(带 Cookie) 老客人 s01,第 2 次来
乙 第 1 次(新连接无 Cookie) 新客人,发给你 s02
服务端记下的会话数 2 个
甲被认出的次数 3 次(含首次)
同一个人的三次请求里,**只有后两次带 Cookie**;
乙虽然是同一台机器、同一个浏览器进程,因为没有那个标识,就被当成新客人。
结论:**识别身份靠客户端每次主动出示标识**,服务端只负责发和验。
`HttpOnly` 让 JavaScript 读不到这个 Cookie,是防 XSS 偷 Cookie 的第一道墙。
甲 第 1 次 新客人,发给你 s01 → 响应带 Set-Cookie: SID=s01; Path=/; HttpOnly。
甲 第 2 次(带 Cookie)老客人 s01,第 1 次来 —— 认出来了。
注意计数从 1 开始,因为首次那次服务端只发了 Cookie、没算访问。
乙 第 1 次(新连接无 Cookie)新客人,发给你 s02 —— 同一台机器、同一个程序,没带那个字符串,
就是另一个人。
服务端记下的会话数 2 个、甲被认出的次数 3 次(含首次)。
这几个数把「Cookie 就是身份证」这件事说得最清楚。
30 秒实验
- 把
HttpOnly去掉,想想 XSS 之后攻击者能拿到什么。 - 给 Cookie 加上
Max-Age=10,等 11 秒后再发请求,看客户端还在不在带它(这里要手动模拟, 因为我们是裸 socket)。 - 把
SameSite=Strict加进 Set-Cookie,想想它会挡住哪些场景(从别的站点点链接过来, 第一次请求不带 Cookie)。
常见误区
- 「Cookie 是客户端的存储」——它是服务端设置、客户端保管并自动回传的, 不是给 JS 用的本地存储(那是 localStorage)。
- 「Cookie 能存很多」——单个域通常限 4 KB,总数也有限。大东西放不下。
- 「Cookie 加密了」——它默认是明文。所以必须
Secure+ HTTPS。
自测三问(点开对答案)
1. 一次 Cookie 会话靠哪两个首部完成? —— 服务端发 `Set-Cookie`,客户端之后每次请求带 `Cookie`。 2. `HttpOnly` 防的是什么? —— 防 JavaScript 读取,也就是防 XSS 把会话标识偷走。 3. 为什么说 Cookie 有固定开销? —— 同域同路径的每个请求都会自动带上它,页面请求越多,重复传输越多。第二章到此结束。第三章往报文内部走:编码、分块、多部分、范围请求。
- ☐ (本章完,下一节 3.1 HTTP报文)
相关笔记
- HTTP是不保存状态的协议
- 19 课 · 网络与信息安全(会话劫持与 CSRF)
- 📘 图解HTTP · 索引