专栏:AI 全栈开发|10
|--------------------------------------------------------------------------------------------------------------------------------------|
| 上一篇我们已经把 REST API 的资源、Method 和状态码设计好了,但还有一个问题没解决:`GET /me` 到底怎么知道请求来自谁?这一篇先不讲 JWT 细节和 OAuth,只把 Cookie、Session、Token 三个最容易混的概念真正拆开。 |
一、为什么登录成功一次,下一次请求还要"证明你是谁"
用户输入账号密码,服务器验证成功,这只能说明这一次登录请求通过了。
后面用户再请求:
GET /me
GET /orders
POST /checkout
这些请求本身不会自动带着"刚才登录成功"的记忆。
HTTP 的请求语义本身是无状态的:服务器不能仅凭"这是下一条 HTTP 请求"就知道它和上一条是不是同一个登录用户。
所以应用需要一种可验证的标记,让后续请求能重新建立身份上下文。
二、先把三个词摆正位置
图 1 Cookie、Session、Token 不在同一层级:一个是浏览器状态机制,一个是服务端会话状态,一个是凭证形式
Cookie:浏览器怎么保存并自动携带一小段数据
服务器可以通过 Set-Cookie 响应头让浏览器保存 Cookie。后续请求满足 Domain、Path、SameSite、Secure 等条件时,浏览器会自动携带对应 Cookie。
Session:服务器怎么保存一段会话状态
Session 通常表示服务端保存的"这次会话是谁、状态是什么"。浏览器往往只拿一个随机 session id,真正的用户状态留在服务器。
Token:客户端拿什么凭证来证明身份或权限
Token 是一种凭证。它可以是 JWT 这种带 claims 的自包含形式,也可以只是一个随机字符串(opaque token),服务器再去查存储或做 introspection。
|-------------------------------------------------------------------------------------|
| 最容易记错的一点:Token 不等于 JWT;Token 也不等于一定放 localStorage。Token 完全可以放在 HttpOnly Cookie 里发送。 |
三、Cookie 到底是怎么工作的
登录成功后,服务器可以返回:
|----------------------------------------------------------------|
| Set-Cookie: sid=abc123; Path=/; HttpOnly; Secure; SameSite=Lax |
浏览器收到以后,会按 Cookie 规则保存它。后续访问匹配的 URL 时,浏览器自动发送:
|--------------------|
| Cookie: sid=abc123 |
所以 Cookie 的价值主要在"浏览器帮你保存并按规则带回服务器"。
它本身不规定 sid 代表什么。你可以放 session id,也可以放偏好设置;在某些设计里也可以放认证 token。
四、Session:Cookie 里通常只放"钥匙"
图 2 常见 Cookie Session:浏览器带 session id,服务器用它查真正的会话状态
最常见的 Session 登录是:
• 服务器验证账号密码。
• 生成随机 session id。
• 服务端保存 session_id → user_id / expires_at / roles...。
• 把 session id 放进 Cookie。
• 后续请求带 Cookie,服务器查 Session Store,重新得到当前用户。
所以 Cookie 和 Session 经常一起出现:
Cookie 负责运送钥匙;Session 负责保存钥匙背后的可信状态。
五、Session 状态一定要放 Redis 吗
不一定。
学习 Demo 可以放进程内存;小型单实例应用也可能放数据库;多实例服务如果每台机器都要识别同一个 session,就需要共享存储、粘性会话,或者其他能让状态一致的设计。
Redis 是常见实现之一,但不是 Session 的定义。
真正应该先问的是:
• 哪些状态必须在服务器保存?
• 部署多个实例以后,哪些实例需要读到同一份状态?
• Session 过期、退出登录、踢人下线时怎么删除或失效?
六、Token:为什么不能简单等于"无状态 JWT"
图 3 Token 有不同实现:JWT 是一种,opaque token 也是一种;它们的验证方式不同
JWT 很常见,所以很多教程会直接把 Token 和 JWT 画等号。这个习惯容易把基础概念学歪。
Token 更宽泛:
自包含 Token
例如 JWT。凭证里带有 claims、过期时间等,服务器验证签名等信息后可以直接得到一部分身份数据。
Opaque Token
客户端看到的只是随机字符串。服务器收到以后,需要查自己的 Token Store,或者去授权服务器 introspection,才能知道它代表谁。
因此"Token 验证一定不查数据库"并不成立;只有某些自包含 token 设计才可能不为每次请求查询会话记录。
七、Token 到底放哪里
另一个很常见的误区是:
Session → Cookie,Token → localStorage + Authorization。
这不是固定规则。
Token 可以:
• 放在 Authorization: Bearer ... 里,由 JavaScript / 客户端显式发送。
• 放在 HttpOnly Cookie 里,由浏览器自动发送。
• 原生 App 还可能放系统安全存储,再加到请求头。
选哪里取决于客户端类型、XSS / CSRF 风险模型、跨域架构和后端接口设计。完整 Auth 安全会在后面的安全章节展开。
八、HttpOnly、Secure、SameSite 到底各解决什么
|----------|------------------------------|--------------------|
| 属性 | 主要作用 | 不要误解成 |
| HttpOnly | 禁止页面 JavaScript 直接读取该 Cookie | "有它就不会受 XSS 影响" |
| Secure | 只通过安全连接发送 Cookie | "自动解决所有中间人/应用安全问题" |
| SameSite | 限制 Cookie 在部分跨站请求中的携带方式 | "开启以后 CSRF 就彻底消失" |
这些属性都很重要,但它们各自只解决一部分风险。
例如 HttpOnly 能降低认证 Cookie 被脚本直接窃取的风险,但如果页面本身发生 XSS,攻击脚本仍可能借用户身份发起操作。SameSite 能显著降低很多 CSRF 场景,但复杂跨站登录、第三方集成仍要具体设计。
九、用最小 FastAPI 代码看懂 Cookie Session
这一篇不实现完整密码系统,只演示 Session 关系:
import secrets
from fastapi import FastAPI, Cookie, HTTPException, Response
app = FastAPI()
sessions: dict[str, str] = {}
@app.post("/login")
def login(response: Response):
user_id = "u_1001" # 假设账号密码已经验证成功
sid = secrets.token_urlsafe(32)
sessions[sid] = user_id
response.set_cookie(
"sid",
sid,
httponly=True,
secure=True,
samesite="lax",
)
return {"ok": True}
@app.get("/me")
def me(sid: str | None = Cookie(default=None)):
user_id = sessions.get(sid or "")
if not user_id:
raise HTTPException(401, "not logged in")
return {"user_id": user_id}
从这段代码只看一件事:浏览器只有 sid,服务器才保存 sid → user_id 的映射。
这已经足够理解 Cookie Session,不需要在第 10 篇同时实现注册、密码哈希、Redis、滑动过期、JWT 和 OAuth。
十、Cookie、Session、Token 应该怎么对比
|-----------|----------------|-----------------------|---------------------------------|
| 问题 | Cookie | Session | Token |
| 是什么 | 浏览器的 HTTP 状态机制 | 服务端会话状态 | 认证/授权凭证的一种形式 |
| 主要存在哪里 | 浏览器 | 服务器 | 客户端持有;验证信息可能在 token 内,也可能在服务端 |
| 常见传输 | Cookie 头 | session id 常通过 Cookie | Authorization Header 或 Cookie 等 |
| 是否一定可主动吊销 | 取决于里面放什么 | 通常容易通过删除服务端状态吊销 | 取决于 token 设计与验证策略 |
| 是否等于登录方案 | 不是 | 不是单独协议 | 不是 |
十一、这一篇先不要做"Session vs Token 二选一"
真实产品经常组合使用:
• Web 后台使用 Cookie + Server Session。
• API 客户端使用 Bearer Token。
• 浏览器也可以把短期 Token 放 HttpOnly Cookie。
• OAuth 流程最终也可能得到 access token,再由客户端携带。
所以这一篇真正要建立的不是"哪个更好",而是先把三个概念的层级分清。
十二、几个最容易学错的说法
• 误区 1:Cookie、Session、Token 是三个并列登录方案。它们处在不同层级,可以组合。
• 误区 2:Cookie 里放 user_id,服务器直接信任就行。客户端可修改的数据不能直接当可信身份。
• 误区 3:Token 就是 JWT。JWT 只是 Token 的一种实现。
• 误区 4:Token 一定放 localStorage。它也可以通过 Cookie 传输。
• 误区 5:Token 一定完全无状态、一定无法吊销。Opaque Token 本来就依赖服务端状态;JWT 也可以通过黑名单、版本号等引入吊销机制。
• 误区 6:HttpOnly + Secure + SameSite 就等于认证安全完成。它们只是 Cookie 安全设计的一部分。
十三、下一篇
下一篇 11《HTTP Streaming 为什么对 AI 应用这么重要?》会换到另一条 Web 基础线:为什么模型生成 10 秒,页面却不应该白等 10 秒?HTTP Response 怎样边生成边返回,Browser 又怎样一块块消费这些数据?