凤凰架构 · 第 5 章 架构安全性 —— 苏格拉底式学习笔记
来源:《凤凰架构:构建可靠的大型分布式系统》(周志明) 学习方式:苏格拉底式提问会话(本场覆盖 5.1 认证 / 5.2 授权(部分)/ 5.3 凭证 / 5.5 传输)
一、安全设计的六个基本问题
| 问题 | 英文 | 一句话 |
|---|---|---|
| 认证 | Authentication | 你是谁?(分辨用户真实身份) |
| 授权 | Authorization | 你能干什么?(控制可见数据与可操作功能) |
| 凭证 | Credential | 你如何证明?(系统与用户之间的承诺准确、完整、不可抵赖) |
| 保密 | Confidentiality | 敏感数据如何不被窃取、滥用? |
| 传输 | Transport Security | 网络传输的信息如何不被窃听、篡改、冒充? |
| 验证 | Verification | 提交的数据是否合乎规则、不带来风险? |
经验原则:安全上 99% 的系统都不该造轮子——以标准规范为指导、以标准接口去实现。
二、核心概念辨析
1. 认证 vs 授权 vs 凭证
- 认证是一次性的"过程":登录那一刻,服务端核实你的身份。
- 凭证是"认证结果"的物化载体:把"你曾通过认证"这个事实固化下来,让之后的每次请求免于重复认证。
- 授权:认证之后,决定你能碰什么。
关键洞察:服务端后续每次请求做的不是"认证",而是"验证凭证"——它默认持有有效凭证的人 = 当初通过认证的人。 例:攻击者偷走 Session ID 直接发请求,服务端照常放行——因为验证的是凭证,不是人。
2. Session vs JWT:状态放服务端还是客户端
| 维度 | Cookie-Session | JWT |
|---|---|---|
| 状态存在哪 | 服务端(内存/存储) | 客户端(令牌自带 payload) |
| 验证方式 | 查表(sessionId → 用户状态) | 用密钥重算签名验证有效性 |
| 信任源头 | 服务端存储 | 签名密钥保密 |
| 强制下线 | 删掉服务端 session 即可 | 做不到,只能加黑名单 |
| 水平扩展 | 需处理 session 共享(亲和/复制/集中三种,均受 CAP 限制) | 天然无状态,任意加减节点 |
| 携带信息 | 服务端随意 | 受 Header 大小限制(Tomcat 8KB / Nginx 4KB) |
关键洞察:"能否强制下线 / 能否主动失效 / 能否无状态扩展"全是"状态在哪"这一个区别推出来的。 JWT 是 Cookie-Session 在认证授权问题上的替代品,不是更先进,也不能全面取代——它只解决认证授权,且默认不加密(只防篡改、不防泄漏)。
3. 对称 vs 非对称签名(HMAC → RSA)
- HMAC(HS256):签发与验证用同一个密钥(带密钥的哈希摘要)。
- 后果:所有验证方都必须持有同一把密钥 → 密钥副本多、泄漏面大;一个最弱环节被攻破 = 全系统凭证体系崩塌(可伪造任意用户的合法 JWT)。
- RSA(非对称):私钥签发(集中)、公钥验证(公开)。
- 后果:秘密只集中在签发服务;公钥泄漏了也造不出合法令牌。
关键洞察:微服务体系里签名从 HMAC 换 RSA,是因为信任支点从"所有服务共享一个秘密"变成了"秘密集中在签发方"。 副本越少,泄漏面越小。
4. 公钥分发的死循环与 PKI
- 问题:公钥"公开"也得通过网络分发 → 中间人截获"拉公钥"的请求,返回自己的公钥 → 此后用它验证的一切签名全部失守。
- 为什么不能"用私钥给公钥签名来证明":签名只能传递信任,不能凭空创造信任——验证签名本身又需要先信任另一个公钥 → 无限递归(蛋鸡悖论)。
- 解法:锚点 = 出厂预置的根 CA 证书。
- 权威 CA 是"可数"的(全世界就那么些),可以不通过网络,提前装进浏览器/操作系统/服务节点的信任存储里。
- 链条:预置的根证书(不需要证明)→ 验证 CA 签发的证书上的签名 → 取出服务真正的公钥。
- 信任的终点不是"证明",而是"物理预置 / 带外分发"。
任何人(包括你我)都可以签发证书,只是不权威罢了。
5. keystore vs truststore(每个服务节点上都有两个库)
| 存储 | 放什么 | 回答的问题 | 私钥 |
|---|---|---|---|
| keystore | 自己的证书(含自己的公钥)+ 自己的私钥 | "我是谁" | 绝对不出示、不传输 |
| truststore | 受信任的 根 CA 证书(含根公钥) | "你是谁"(我信任谁) | 无 |
类比:keystore = 我的身份证 + 签名章;truststore = 派出所档案室(我信任的机构名单)。
6. mTLS 的两条信任链(两个公钥、两个签名、两个时间点)
| 签名 ① 证书签名 | 签名 ② JWT 签名 | |
|---|---|---|
| 谁签的 | CA 用根私钥 | account 服务用自己私钥 |
| 用什么验证 | truststore 里根证书的公钥 | 从 B 证书里取出的 account 公钥 |
| 验证什么 | B 的身份(证书是真的) | 令牌内容(令牌可信) |
| 何时发生 | 建立连接时(TLS 握手) | 每次请求时(HTTP 层) |
完整链条:
A 用 truststore 里根证书的公钥 验证 B 证书上的签名,确认身份,取出 account 的公钥;A 再用 account 的公钥 验证 JWT 上的签名,确认令牌可信。
两道关卡,缺一不可:
- ① 证书必须由受信任的 CA 签发 → 挡住"假证书";
- ② 通信方必须持有与证书公钥配对的私钥 → 挡住"偷了真证书但没私钥"的冒名者。
- 前提:私钥永远不出 keystore。
三、本场掉过的坑(常见误区清单)
- ❌ "认证 = 输入账号密码的过程" → ✅ 认证是一次性过程;后续验证的是凭证,免于重复认证。
- ❌ 把信任锚点说成"客户端证书" → ✅ 锚点是根 CA 证书;服务证书是被签发的中间产物,不是锚点。
- ❌ 根 CA 证书放 keystore → ✅ 验证别人的东西放 truststore;keystore 只放自己的证书 + 私钥。
- ❌ "重新签名去和原签名比对" → ✅ 验证 ≠ 比对:验证不需要知道原签名,只需用公钥重算并校验有效性。
- ❌ 证书签名和 JWT 签名混为一谈 → ✅ 两个签名、两个公钥、两个验证时机,分属"身份"与"内容"两条链。
四、一句话总结(复习用)
- 凭证的本质:把"认证结果"物化,让后续请求免于重复认证;命门在服务端凭什么相信凭证有效。
- Session vs JWT:状态在服务端(查表)vs 状态在客户端(验签)——一切差异由此而来。
- 对称 → 非对称:把秘密从"人手一份"收敛到"集中一处",缩小泄漏面。
- PKI 的答案:签名不能创造信任,锚点靠出厂预置的根 CA 证书一刀斩断死循环。
- mTLS 两条链:证书验身份(连接时)、公钥验令牌(请求时);假证书和偷真证书无私钥都过不了关。