Cookie 是给无状态协议补「记忆」的机制:服务端下发,客户端自动回送。 JavaScript 跑在浏览器里,CGI 跑在服务器上,两者位置完全不同。
这一节用真的 cookiejar 跑一遍,让服务端把收到的 Cookie 头打出来。 再摆清 JavaScript、CGI、HTML 三者的分工。
一、Cookie 是怎么被自动带上的
整个过程只有两步:
服务端在响应头里塞一句 Set-Cookie;客户端存下来,之后每次请求自动附加。
注意「自动」这两个字:这个附加过程不经过 JavaScript,
是浏览器(或 urllib 的处理器)在发请求前做的。
所以你在开发者工具里能看到它,但你在页面代码里找不到是谁写的。
这也是「禁用 Cookie」会导致登录失效的原因:
服务端认的是那个 Cookie,不是你的 IP、不是你的浏览器指纹。
一旦客户端不回送,服务端每次看你都是新访客。
二、五个属性,两个和安全有关
Domain 管「发给哪些主机」、Path 管「发给哪些路径」、Expires 管「什么时候过期」。
不写 Expires 就是「关浏览器就没」,这类叫会话 Cookie。
后两个是安全属性,也是最常被漏掉的两个:Secure 要求只走 HTTPS,
HttpOnly 要求 JavaScript 读不到。
没有 Secure 的后果很直接:同一个 Cookie 会在 HTTP 明文请求里裸奔,
路径上任何一个节点都能读到它。
所以「登录态 Cookie 必须带 Secure」是硬要求,这一条在几乎所有安全规范里都排在最前面。
没有 HttpOnly 的后果更隐蔽:一段注入的脚本就能把它读走,然后发到攻击者的服务器上。
加上 HttpOnly 之后,脚本连读都读不到,即使有 XSS,也偷不走登录态。
这是「纵深防御」的典型做法:假设第一道防线会被突破。
三、三样东西各跑在哪儿
HTML 跑在浏览器里,描述结构;JavaScript 跑在浏览器里,管行为; CGI 跑在服务器上,收请求、跑程序、拼 HTML 回去。 这三行就是 1993 年到 2005 年 Web 的全部架构: 页面是服务器拼好的,浏览器只负责显示和一点点校验。 所以那时候「表单验证」要在两边各写一遍—— 前端为了体验(不用等一次往返),后端为了安全(前端可以被绕过)。 今天的「前后端分离」把第二行放大了几十倍:HTML 变成了一张空壳,内容全靠 JavaScript 去取。 但底下的 HTTP 还是同一个 HTTP,Cookie 也还是同一套 Cookie。 所以「框架换了三代,协议没动」这个观察,是理解 Web 技术演进的最好角度。
四、动手 Demo:Cookie 是怎么被「自动带上」的
用真的 cookiejar 连发三次请求,服务端把收到的 Cookie 头记下来。 看第一次为什么是空的。
import http.cookiejar, urllib.request
seen = []
port, srv = start_server(record=seen) # 服务端把 Cookie 头记进 seen
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(jar))
for i in range(3):
with opener.open('http://127.0.0.1:%d/p%d' % (port, i), timeout=5) as r:
r.read()
for i, c in enumerate(seen, 1):
print('第 %d 次:%s' % (i, c or '(没有 Cookie 头)'))
实跑输出:
--------------------------------------------------------------------
Cookie 是怎么被「自动带上」的
--------------------------------------------------------------------
**HTTP 无状态,于是有了 Cookie**:
**服务端在响应头里塞一句 `Set-Cookie`,浏览器存下来,之后每次请求自动回送。**
**下面用真的 cookiejar 跑一遍,并让服务端把收到的头打出来。**
**服务端收到的 Cookie 头,按请求顺序**:
第 1 次请求:(没有 Cookie 头)
第 2 次请求:sid=abc123
第 3 次请求:sid=abc123
**第一次是空的,第二三次都带上了。**
**因为第一次响应的 `Set-Cookie` 被 jar 存了下来,之后自动附加。**
**这个过程不经过 JavaScript**,**是浏览器(或 urllib 的处理器)在发请求前自动附加的。**
**所以在开发者工具里能看到它,但你在页面代码里找不到是谁写的。**
**Cookie 有五个属性,各管一件事**:
属性 管什么 不写会怎样
---------------------------------------------------------------------------------
`Domain` **这个 Cookie 能发给哪些主机** **不写就是「只发给当前主机」**
`Path` **能发给哪些路径** **写 `/` 就是全站**
`Expires` **什么时候过期** **不写就是「关浏览器就没」**
`Secure` **只走 HTTPS** **不加就会被明文链路泄露**
`HttpOnly` **JavaScript 读不到** **防 XSS 偷 Cookie**
**后两个是安全属性,也是最常被漏掉的两个**:
**没有 `Secure`,同一个 Cookie 会在 HTTP 明文请求里裸奔。**
**没有 `HttpOnly`,一段注入的脚本就能把它读走。**
**再说 JavaScript 和 CGI 在这套体系里的位置**:
东西 跑在哪 能做什么
--------------------------------------------------------------------------
**HTML** **在浏览器里** **描述结构,不算逻辑**
**JavaScript** **在浏览器里** **能改页面、能发请求、能读 Cookie**
**CGI** **在服务器上** **收请求、跑程序、拼 HTML 回去**
**这三行是 1993 年到 2005 年 Web 的全部架构**:
**页面是服务器拼好的,浏览器只负责显示和一点点校验。**
**今天的「前后端分离」把第二行放大了几十倍**——**HTML 变成了一张空壳,内容全靠 JavaScript 去取。**
**但底下的 HTTP 还是同一个 HTTP,Cookie 也还是同一套 Cookie。**
第一次是空的,第二三次都带上了 sid=abc123。
因为第一次响应的 Set-Cookie 被 jar 存了下来,之后自动附加。
这个「第二次才带上」的顺序很重要:它说明 Cookie 是「服务端先给、客户端后还」的单向流程。
所以「客户端能不能自己造一个 Cookie」这个问题的答案是「能」——Cookie 没有任何防伪机制,
服务端必须自己校验它的内容(签名或加密)。
这也是「Cookie 里不要放敏感明文」的原因:它只是一个「客户端可以随便改的字符串」,
不是「服务端存在你那里的凭证」。
所有「防篡改」的责任都在服务端。
30 秒实验
- 想一下:把 Cookie 删掉再请求一次,服务端还认得你吗?
- 想一下:
HttpOnly能防住什么?防不住什么? - 想一下:为什么表单验证要在前后端各写一遍?
常见误区
- 以为「Cookie 只能由服务端设置」。 客户端也能造,所以服务端必须校验。
- 以为「加了
HttpOnly就安全了」。 它只防读, 防不住 CSRF 这类「借用你的 Cookie 发请求」。 - 以为「Cookie 越大越好」。 每个请求都要带,太大会拖慢所有请求。
自测三问(点开对答案)
1. Cookie 的两步流程是什么? —— **服务端下发 `Set-Cookie`,客户端之后每次请求自动回送。** 2. `Secure` 和 `HttpOnly` 各防什么? —— **`Secure` 防明文链路泄露**;**`HttpOnly` 防脚本读取。** 3. CGI 和 JavaScript 各跑在哪? —— **CGI 在服务器上,JavaScript 在浏览器里。**下一节换到网络管理:SNMP。
- ☐ (本节完,下一节 8.6.1 SNMP)