08 - 绕不开的安全问题
登录系统天生是攻击者最想攻破的地方——毕竟它是”进门的钥匙”。这一章讲三个绕不开的安全话题:密码怎么存、凭证怎么被偷、Cookie 怎么加固。
依然是重思维:你不需要背具体算法,但必须理解”为什么要这么做”。
一、密码:永远存哈希,永远不存明文
这是登录安全的第一铁律,没有之一。
❌ 数据库里存:password = "123456"
✅ 数据库里存:hashed_password = "$2b$12$LJ3m4ys..."(一串没法还原的哈希)
为什么? 因为数据库总有泄露的风险(被拖库、内部人员窥探)。如果存明文,一旦泄露,所有用户的密码直接暴露。更糟的是——很多人所有网站用同一个密码,你的库泄露会连累用户在别处的账号。
哈希的特点是单向不可逆:
明文 "123456" ──哈希──▶ "$2b$12$LJ3m..." (能算过去)
"$2b$12$LJ3m..." ──?──▶ "123456" (算不回来)
那登录时怎么验证?——不是把哈希还原成明文比对,而是把用户输入的密码也哈希一遍,比两个哈希值:
登录:用户输入 123456
│ 哈希一下
▼
算出的哈希 == 数据库里存的哈希?
├─ 相等 → 密码对 ✅
└─ 不等 → 密码错 ❌
两个进阶点:加盐 和 用对算法
加盐(Salt):如果直接哈希,两个人密码都是 123456,存进去的哈希就一样,攻击者可以用”彩虹表”(预先算好的常见密码哈希对照表)反查。加盐就是给每个密码掺一段随机字符再哈希:
张三:123456 + 盐"x8f2" → 哈希A
李四:123456 + 盐"q9k1" → 哈希B ← 同样的密码,哈希完全不同,彩虹表失效
用对算法:不要用 MD5、SHA1 这种为”快”设计的算法——快意味着攻击者也能飞快地暴力破解。要用 bcrypt / scrypt / argon2 这类故意设计得慢的算法。慢一点点对正常登录无所谓(你一次只登一下),但对想每秒试几百万次的攻击者是致命的。
好消息:bcrypt 这类库自动帮你加盐、自动控制速度。你只要用对库(如第 09 章 FastAPI 认证章节里的
passlib+ bcrypt),这些细节它都替你处理了。你要记住的是”为什么”。
二、凭证怎么被偷:XSS 和 CSRF
密码存好了,但登录后那张”凭证”(Session ID 或 token)本身也是攻击目标——偷到凭证,等于不用密码就能冒充你。两种经典攻击:
XSS(跨站脚本):偷 token
攻击者想办法往页面里注入一段恶意 JS(比如在评论区发一段脚本,别人打开就执行)。这段脚本能读取浏览器里的数据:
如果你的 token 存在 localStorage 里
│
恶意脚本:读取 localStorage.token,偷偷发给攻击者的服务器
│
攻击者拿着你的 token,冒充你 😱
防御思路:
- 对用户输入做转义/过滤,别让恶意脚本能被注入执行(这是根治)。
- 敏感凭证尽量别放 JS 能读到的地方。用 Cookie 存并加上
HttpOnly(见下文),JS 就读不到它。
CSRF(跨站请求伪造):冒用你的 Cookie
这个专门坑 Cookie 方案。回想第 03 章,浏览器会对某网站的每个请求自动带上该网站的 Cookie——问题就出在”自动”上:
你已经登录了 bank.com,浏览器存着它的 Cookie。
│
你被诱导点开一个恶意网站 evil.com。
│
evil.com 页面里藏了一个请求:向 bank.com/transfer 转账
│
浏览器发这个请求时,"自动"带上了你 bank.com 的 Cookie!
│
bank.com 一看 Cookie 有效 → 以为是你本人操作 → 转账成功 😱
注意:攻击者根本没偷到你的 Cookie,他只是”借用”了浏览器自动携带 Cookie 的机制。
防御思路:
- 给 Cookie 加
SameSite属性(见下文),让它在跨站请求时不自动带上。 - 关键操作加 CSRF Token(一次性随机数校验)等额外验证。
- 顺带一提:JWT 放在请求头里、需要前端手动带,天然不受 CSRF 影响(因为恶意网站没法帮你手动加请求头)——但它换来的是 XSS 风险。两条路线各有各的怕。
三、Cookie 的三个”护身符”
如果你用 Cookie(Session 方案),务必给它加上这三个属性,成本极低、收益极大:
| 属性 | 作用 | 防的是 |
|---|---|---|
HttpOnly | 让 JS 读不到这个 Cookie | XSS 偷 Cookie |
Secure | 只在 HTTPS 加密连接下才发送 | 中间人在网络上窃听 |
SameSite | 跨站请求时不自动带上 Cookie | CSRF |
response.set_cookie(
"session_id", session_id,
httponly=True, # JS 碰不到,防 XSS 偷 cookie
secure=True, # 只走 HTTPS
samesite="lax", # 跨站不自动带,防 CSRF
)
一行配置,三重防护。这是”性价比”极高的安全措施,别偷懒不加。
一张图看清”攻击 vs 防御”
攻击目标 攻击手段 防御
────────────────────────────────────────────────────
数据库里的密码 ──▶ 拖库、彩虹表 ──▶ 哈希 + 加盐 + bcrypt
登录后的 token ──▶ XSS 注入脚本偷 ──▶ 输入转义 + HttpOnly Cookie
浏览器的 Cookie ──▶ CSRF 冒用 ──▶ SameSite + CSRF Token
传输途中的凭证 ──▶ 网络窃听 ──▶ 全站 HTTPS + Secure
小结
- 密码铁律:永远存哈希、不存明文;哈希要加盐、用 bcrypt/argon2 这类”故意慢”的算法(库会自动帮你做)。
- 登录后凭证本身也是攻击目标,偷到凭证 = 不用密码就能冒充你。
- XSS:注入恶意脚本偷 token → 靠输入转义 + 把凭证放进 JS 读不到的地方(HttpOnly Cookie)。
- CSRF:借用浏览器自动带 Cookie 的机制冒用你 → 靠
SameSite+ CSRF Token。 - Cookie 三护身符:
HttpOnly+Secure+SameSite,一行配置三重防护,务必加。 - 记住:Session(Cookie)怕 CSRF,JWT(localStorage)怕 XSS,没有绝对安全,只有针对性防御。
最后一章正式内容:进阶场景。我们看看真实产品里那些常见需求——“自动续期不用频繁登录""同一账号最多登 3 台""一次登录多个系统通用”——在架构上是怎么落地的。
上一章 ← 07 - 退出登录:看似简单,其实最难 | 下一章 → 09 - 进阶场景:双令牌 / 多端登录 / SSO | 回到 README 目录