Cookie 是浏览器上的一张便条,服务端用 Set-Cookie 下发、浏览器自动带回。
Flask 的 Session 是把整个会话明文序列化后签名塞进 Cookie,服务器不存东西。
路由函数返回字符串只是最简写法,真正的返回值永远是 Response 对象。
这一篇对应教材 1.4、1.5、1.6 三节:Cookie、Session、Response,是第 1 章的收尾, 也是第 3 章登录功能的基础。 上一篇的 flash 已经用到了会话,这一篇把会话本身摊开看。
一、Cookie:一次下发,之后每次自动带
服务端在响应头里写 Set-Cookie: theme=dark; Max-Age=3600; HttpOnly,浏览器存下来,
之后每次请求自动放进 Cookie 头。
服务端用 request.cookies.get("theme") 读,读不到给默认值。
三个属性最要紧:Max-Age 决定活多久,Path 决定哪些路径带它,HttpOnly 决定 JS 能不能读。
HttpOnly 不是可选项。身份相关的 Cookie 不加它,一个 XSS 就能用 document.cookie 把它偷走。
二、Session:一个签名过的 Cookie
Flask 默认的会话实现,是把整个会话字典序列化、签名,塞进一个叫 session 的 Cookie 里。
服务器这边不存任何东西。
所以「会话」这个名字有点误导。它不是服务器上的一个抽屉,是一张写着你信息的纸,盖了个防伪章。
这次实测把 Cookie 拆开看过:三段,第一段是内容、第二段是时间戳、第三段是签名。
第一段 base64 解开就是明文 JSON。
明文这两个字要记住。内容谁都读得到,只是改不了。所以密码、身份证号、支付信息,一律不许进会话。
三、签名能挡住什么,挡不住什么
能挡住「篡改」:改内容,签名对不上,Flask 直接把整个会话当空的,不报错,就是空。 挡不住「读取」:内容明文,用户自己能看到里面有什么。 也挡不住「重放」:把别人的整个 Cookie 原样拿来用,照样能通过验签。 所以会话 Cookie 要配 HTTPS,防止在路上被抄走。
SECRET_KEY 泄露等于所有人身份都能被伪造。它必须进环境变量,
绝不能进仓库——这是上一篇那条规矩的真正原因。
四、Response:真正的返回值
返回字符串、返回元组、返回 Response 对象,三种写法 Flask 都收。越往后控制力越强。
| return 什么 | 结果 |
|---|---|
| "ok" | 200,text/html |
| ("ok", 201) | 201,text/html |
| ({"a": 1}, 200) | 200,application/json |
| ("ok", 200, {"X-A": "1"}) | 200,带自定义头 |
所有写法最后都被转成同一个 Response 对象。想看头、想加 Cookie、想改状态码,就得拿到它:
make_response(...)。
这次实测用一个 Response("正文", status=418, mimetype="text/plain"),再挂一个自定义头,
返回去照样是合法的 HTTP 响应。
五、重定向与状态码
redirect(url_for(...)) 返回 302,浏览器收到就再发一次请求到新地址。
POST 之后必须重定向(PRG 模式:Post-Redirect-Get),否则用户刷新会重复提交。
url_for 在这里和重定向配合是标准姿势。手写路径也能跳,但改路由就断。
abort(404) 是另一条路:不发跳转,直接结束并返回指定状态码。第 10 章做自定义错误页就是拦它。
六、动手 Demo:Cookie、Session 与 Response
这一节四段:下一张 Cookie 再读回来、登录写入会话、把会话 Cookie 拆开看、最后比较四种返回写法。
r = make_response("cookie set")
r.set_cookie("theme", "dark", max_age=3600, httponly=True)
session["uid"] = 7
session["name"] = "张三"
return jsonify({"ok": True})
实跑输出:
--------------------------------------------------------------------
Cookie、Session 与 Response
--------------------------------------------------------------------
① Cookie:存在浏览器上的一张便条
--------------------------------------------------------------------
响应头 Set-Cookie -> theme=dark; Expires=Tue, 03 Nov 2026 04:26:36 GMT; Max-Age=3600; HttpOnly; Path=/
下一次请求自动带回来 -> theme=dark
! httponly 表示 JS 读不到它。凡是身份相关的 Cookie 都该加上——否则一个 XSS 就能把它偷走。
② Session:签名过的 Cookie
--------------------------------------------------------------------
登录前会话内容 -> {}
GET /login -> 302,跳 /who
跟过去 -> uid=7 name=张三
会话内容 -> {'name': '张三', 'uid': 7}
! Session 不是存在服务器上的。
Flask 默认把整个会话序列化、签名、塞进一个叫 session 的 Cookie 里。服务器不存东西,只验签。
③ 会话里到底装了什么
--------------------------------------------------------------------
登录响应下发的 Cookie(截断)-> session=eyJuYW1lIjoi5byg5LiJIiwidWlkIjo3fQ.aulUbA.3S_S9z...
分成 3 段:第一段是内容,第二段是时间戳,第三段是签名
第一段解出来是明文 JSON:{"name":"张三","uid":7}
! 看清楚:会话内容是明文,只是加了签名。
浏览器和用户都读得到,所以别往里放密码、身份证号这类东西。
把内容改成 uid=9 再拼个假签名 -> uid=None name=None
! 签名对不上,Flask 直接把整个会话当空的。
所以 SECRET_KEY 一旦泄露,别人就能伪造任何人的登录态——它必须进环境变量,不能进仓库。
④ Response:字符串只是最简写法
--------------------------------------------------------------------
jsonify -> 200,Content-Type application/json
正文 {"n":3,"ok":true,"名字":"张三"}
手拼 Response -> 418 text/plain; charset=utf-8,X-Who=demo
redirect -> 302,Location=/who
! 返回字符串、返回元组(正文, 状态码)、返回 Response 对象,三种写法 Flask 都收,
越往后控制力越强。
⑤ 元组写法:正文、状态码、头部
--------------------------------------------------------------------
| return 什么 | 结果 |
|---|---|
| "ok" | 200,text/html |
| ("ok", 201) | 201,text/html |
| ({"a": 1}, 200) | 200,application/json |
| ("ok", 200, {"X-A": "1"}) | 200,带自定义头 |
字典会被自动 jsonify——Flask 1.1 起就有,教材那版也能用。
这一章到此结束。下一章开始上数据库,第一篇就是蓝图:把一堆路由装成一个包。
Set-Cookie 那行里能清楚看到 HttpOnly 和 Max-Age=3600,这两个参数就是安全与生命期。
会话 Cookie 拆成三段,第一段 base64 解开是明文 JSON——「签名不等于加密」这句在这里落地了。
伪造的会话不会报错,会静默变成空会话。所以调试登录问题时要先看会话是不是空的。
最后那张表四种返回写法都跑过,结论是:都能用,但拿不到响应对象就改不了头。
30 秒实验
- 把
httponly=True去掉,看响应头里的Set-Cookie少了什么。 - 给会话加一个
session["cart"] = [1, 2, 3], 看 Cookie 变长多少——想一想为什么不该往会话里塞大对象。 - 用
abort(404)代替return,看 test client 拿到什么状态码。
常见误区
- 往会话里放密码或令牌:内容明文,用户自己能读。
- 以为会话存在服务器上:默认实现是客户端签名 Cookie,重启服务会话不会丢。
- 改了会话忘了
SECRET_KEY要保密:密钥泄露等于所有身份可伪造。 - POST 之后直接返回页面:刷新会重复提交,标准做法是 302 重定向。
自测三问(点开对答案)
1. Flask 的 Session 存在哪? —— 存在客户端的 Cookie 里,序列化后签名,服务器不保存内容。 2. 签名保证了什么、没保证什么? —— 保证内容改不了,没保证内容看不到——内容是明文。 3. 什么时候必须用 `make_response`? —— 要设 Cookie、加自定义响应头、改状态码这类操作时,因为字符串和元组拿不到响应对象。第 1 章到此结束。 下一章上数据库:第一篇讲蓝图,把一堆路由装进一个包,也是整个物件侦测应用的目录起点。
- ☐ (本篇完,下一篇 2.1 目标架构与 Blueprint 分模块)