---
title: 三密钥模型
description: Sudomimus
  如何把一次会话兑换所需的证明拆成三把密钥——exposureKey、hiddenKey、confirmationKey——分别由不同方持有，从而确保兑换合法。
editUrl: true
head: []
template: doc
sidebar:
  order: 2
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

三密钥模型是 Sudomimus 浏览器流程会话安全的核心。每一次经过 Connect 的身份认证往返都会在不同时刻签发**三把彼此独立的密钥**，分别由不同的当事方持有，防御不同类型的攻击。一次成功的 `/redeem` 必须同时出示这三把密钥。

:::note[仅适用于 Connect 浏览器流程]
三密钥模型只适用于 **Connect** 的四阶段生命周期（详见 [Connect 流程](/zh-cn/connect/flow/)）。原生一次性流程（`/direct-issue/steam-ticket`、`/direct-issue/access-key`）不使用该模型，因为客户端提交的凭据本身就是证明。OIDC 则采用标准的 authorization-code + PKCE 流程。本页只讨论 Connect 浏览器流程。
:::

本页讲的不是 Sudomimus 用来签令牌的长期密钥（那部分见 [令牌与验证](/zh-cn/concepts/tokens-and-verification/)）。本页讲的是一次浏览器登录如何证明自己的合法性。

## 三把密钥

| 密钥 | 创建者 | 持有方 | 可见范围 |
|---|---|---|---|
| **exposureKey** | `/establish` | 应用后端 → 浏览器 | 浏览器、URL、`via.sudomimus.com` |
| **hiddenKey** | `/establish` | 仅应用后端 | 后端 |
| **confirmationKey** | 用户完成认证后由 `via.sudomimus.com` 签发 | 应用后端（通过回调） | 浏览器、URL |

`exposureKey` 和 `hiddenKey` 是同一次 `/establish` 调用**成对**签发的。它们属于唯一的一个会话，也只属于那个会话。

每把密钥都带有角色专属的前缀，便于在请求边界直接拒绝错位的密钥：

- `exp_` + 32 位小写 hex
- `hid_` + 32 位小写 hex
- `cnf_` + 32 位小写 hex

## 为什么是三把而不是一把

如果仅凭一个不透明的会话引用就能换取令牌，那么任何得到该引用的攻击者都可以冒充用户完成兑换。将证明拆成三部分后，攻击者必须**同时突破三个不同的信任边界**：

- **没有 `hiddenKey`** —— 即使攻击者窃取了用户正在访问的 URL，也无法兑换令牌。`hiddenKey` 永远不会离开应用服务器。
- **没有 `exposureKey`** —— 即使攻击者攻破了应用服务器，也无法兑换其他用户的会话，因为每个待处理会话的 `exposureKey` 只会发送给对应的浏览器。
- **没有 `confirmationKey`** —— 任何一方都无法兑换用户尚未完成的会话。只有用户在 `via.sudomimus.com` 上通过通行密钥或验证码挑战后，平台才会签发 `confirmationKey`。

## 生命周期

```
/establish ────► exposureKey + hiddenKey
                      │              │
                      ▼              ▼
                 发给浏览器        留在后端
                      │
                      ▼
                  via.sudomimus.com ──► 用户通过挑战
                      │
                      ▼
                 confirmationKey ──► 回到你的回调
                      │
                      ▼
/redeem ◄──── exposureKey + hiddenKey + confirmationKey
                      │
                      ▼
                accessToken + refreshToken
```

`/redeem` 成功之后，这三把密钥都会被消费掉，不能再次使用。即使应用重复发送同一组三元组，第二次也会失败。

## 与 OAuth 2.0 的对照

如果你熟悉 OAuth 的 authorization-code 流程，大致的对应关系是：

| Sudomimus | OAuth 2.0 |
|---|---|
| `exposureKey` | `state` 参数（大致对应） |
| `confirmationKey` | authorization `code` |
| `hiddenKey` | *没有直接对应物* |

OAuth 使用长期存在的 `client_secret` 验证令牌交换请求。Sudomimus 则在每次 `/establish` 时生成新的 `hiddenKey`。某次会话的 `hiddenKey` 泄露只会影响这一次兑换，不会影响应用过去或未来的其他兑换。

这与「持有证明（proof-of-possession）令牌」的思路一致：短期、范围明确的密钥优于长期、被广泛共享的密钥。

如果需要标准的 OAuth/OIDC 语义，可以使用部署在 `oidc.sudomimus.com` 的[标准 OIDC 提供方](/zh-cn/oidc/flow/)。三密钥模型用于底层 Connect 协议，通过 OIDC 接入的应用不会直接接触它。

## 长期密钥对

除了上述三把会话密钥，每个应用还有两对**长期** RSA-2048 密钥对：

- **Token-signing 密钥对** —— Sudomimus 用私钥半边签 access / refresh JWT；应用通过 `POST /info` 拿到公钥半边验证签名。
- **Client-auth 密钥对** —— 应用用私钥半边（在应用创建和每次轮换时一次性下发）签每一次 `/establish` 请求；Sudomimus 用存储的公钥半边校验。

这两对都**不**属于「三密钥」模型。它们认证的是 Sudomimus 与应用之间的**通道**；三把会话密钥认证的是**一次登录**。完整细节见 [令牌与验证](/zh-cn/concepts/tokens-and-verification/)。