# 凤凰架构 · 第 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**。

---

## 三、本场掉过的坑（常见误区清单）

1. ❌ "认证 = 输入账号密码的过程" → ✅ 认证是一次性过程；后续验证的是凭证，免于重复认证。
2. ❌ 把信任锚点说成"客户端证书" → ✅ 锚点是**根 CA 证书**；服务证书是被签发的中间产物，不是锚点。
3. ❌ 根 CA 证书放 keystore → ✅ 验证别人的东西放 **truststore**；keystore 只放自己的证书 + 私钥。
4. ❌ "重新签名去和原签名比对" → ✅ 验证 ≠ 比对：验证不需要知道原签名，只需用公钥重算并校验有效性。
5. ❌ 证书签名和 JWT 签名混为一谈 → ✅ 两个签名、两个公钥、两个验证时机，分属"身份"与"内容"两条链。

---

## 四、一句话总结（复习用）

- **凭证的本质**：把"认证结果"物化，让后续请求免于重复认证；命门在服务端凭什么相信凭证有效。
- **Session vs JWT**：状态在服务端（查表）vs 状态在客户端（验签）——一切差异由此而来。
- **对称 → 非对称**：把秘密从"人手一份"收敛到"集中一处"，缩小泄漏面。
- **PKI 的答案**：签名不能创造信任，锚点靠出厂预置的根 CA 证书一刀斩断死循环。
- **mTLS 两条链**：证书验身份（连接时）、公钥验令牌（请求时）；假证书和偷真证书无私钥都过不了关。
