---
title: 身份认证的设计原则
description: 支撑 Sudomimus 一切其他设计的威胁模型与基本原则。
editUrl: true
head: []
template: doc
sidebar:
  order: 1
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

Sudomimus 把每一道协议边界都视为独立暴露面：浏览器、应用服务器、原生客户端、OIDC 接入方以及它们之间的网络，都可能分别被攻陷。因此，一条流程不应让任何单一参与方掌握足以冒充其他所有参与方的材料。

这些原则横跨 Connect、OIDC 与原生 direct-issue。部分章节以 Connect 作为具体例子；协议细节则放在各自的集成章节中。

## 1. 兑换证明只属于一次会话

许多身份认证系统都会使用长期凭据——OAuth 的 `client_secret`、API key，或应用环境变量中的固定签名密钥。凭据泄露后，在轮换完成前可能持续授权操作；具体影响还取决于协议采用的其他证明与检查。

Connect 不会把某个长期共享密钥本身当成兑换所有登录的充分证明。每次往返都会在 `/establish` 时生成全新的 `hiddenKey`，只在 `/redeem` 中使用一次，随后永久失效。某次会话的 `hiddenKey` 泄露只会影响这一次兑换，不会危及应用过去和未来的所有登录。

这是一种持有证明设计：使用短期、范围明确的密钥，而不是长期、广泛共享的密钥。

> **不采用的方案：** 使用同一个长期 `client_secret` 验证所有令牌交换。

## 2. 身份与凭据彼此独立

Sudomimus 中的 **账户（account）** 代表用户本人及其个人资料。**认证方式（authentication method）** 让用户通过通行密钥、邮箱验证码、Steam 身份或 AccessKey 凭据证明身份。一个账户可以使用多种认证方式。

这样做的好处：

- **邮箱所有权与登录方式相互独立。** 删除一种登录方式本身不会移除已验证邮箱的所有权。枚举防护取决于端点行为、速率限制和流程控制。
- **添加登录方式后，账户身份保持不变。** 用户可以为邮箱验证码账户添加通行密钥，并继续使用同一个账户。
- **登录无需密码。** Sudomimus 支持通行密钥、邮箱验证码、社交登录（Google、GitHub、Discord、Battle.net、X）、Steam 和 AccessKey 凭据。

> **不采用的方案：** 把邮箱作为账户身份，或存储密码记录。

## 3. 身份是不透明的，而且对每个应用都不一样

你的应用拿到的是一个**成对标识符（pairwise identifier）**——一个稳定、不透明的 `sub`，它只在*你这个应用*范围内唯一。同一个人登录两个不同的应用，会拿到两个互不相关的标识符；应用只使用自己扇区内的那个标识。

这样做的好处：

- **无法跨应用关联。** 两个应用即使串通，拿各自的标识符互相比对，也无法把「它们各自的用户」拼成同一个真人。
- **标识符在契约上就是不透明的。** 它是一个用于精确匹配的令牌，而不是一个供你解析的结构化值。Sudomimus 可以改动它的内部格式而不破坏你的接入——正因为你本来就不该从里面读出任何东西。
- **令牌泄露只会暴露某个应用所看到的用户身份**，不会暴露平台级身份。

> **不采用的方案：** 向每个接入方暴露同一个全局用户标识符。

## 4. 用户决定每个应用能了解到什么

个人资料通常通过 UserInfo 返回，并且仅限于**用户已同意分享给那个特定应用**的声明，以及应用策略明确请求的占位值。邮箱、名、姓、头像各自独立受控，且授权随时可以撤销。UserInfo 每次调用都会实时解析策略和授权，因此撤销会立即生效。

这样做的好处：

- **应用请求的声明可能不存在。** 应用必须处理可选声明缺失的情况。如果应用依赖某条声明，请将其设为必需；在用户同意前，Sudomimus 会阻止非交互式签发，而不是返回不完整的 UserInfo。
- **Bearer 凭据暴露得更少。** Access 和 refresh token 不携带个人资料。ID Token 通常也不携带个人资料，只有 OIDC 仅返回 ID Token（`response_type=id_token`）时例外。该响应只包含 scope、当前策略和用户授权允许披露的声明。已签发的 JWT 是快照；撤销授权不能收回已交付的数据。

> **不采用的方案：** 用户登录后向每个应用发布完整资料。

## 5. 本地令牌验证

当应用收到 access token 时，它是一个**签名后的 JWT**。应用从 JOSE header 读取 `kid`、从 payload 读取 `aud`，再从 `GET /applications/{applicationAnchor}/jwks.json` 返回的应用 Session JWK Set 中精确选择对应密钥，并按响应头缓存整组密钥。验证在本地完成，不需要为每个已认证的应用请求调用 Sudomimus。

由此带来：

- **每个已认证请求都不需要额外网络调用**——验证完全在本地完成。
- **登录之后你的服务对 Sudomimus 不存在可用性依赖。**
- **令牌被设计为短期有效**（access token 默认有效期是几个小时，不是几天）。过期时，一次对 `/refresh` 的 HTTPS 调用即可获取替代令牌，无需用户重新认证。

OIDC 的 ID token 是另一种情况：接入方通过 `oidc.sudomimus.com/.well-known/jwks.json` 上的 JWKS 验证签名。完整说明见 [令牌与验证](/zh-cn/concepts/tokens-and-verification/)。

> **不采用的方案：** 每个已认证请求都需要查询远程 IdP 会话。

## 6. 访问采用允许列表，默认拒绝

谁可以完成认证、可以使用哪些方式、结果如何返回，都由显式允许列表控制。未配置规则的应用不会允许任何人登录；访问保持关闭，直到管理员明确启用。

> **不采用的方案：** 默认允许访问，之后再由管理员添加限制。

## 7. 会话尝试次数限制

一种常见的反模式是**账户锁定**：失败次数过多会在一段时间内冻结整个账户。这可以减少暴力破解尝试，但也会形成账户级拒绝服务风险。

每次 Sudomimus 认证会话都有自己的尝试次数。失败的尝试只会消耗**当前会话**的次数，不会锁定账户。次数用尽后，当前会话失效，用户可以开始新的会话。

> **不采用的方案：** 由单次认证会话中的失败触发账户级锁定。

## 8. 密钥保管与兑换证明

要伪造一次成功的 `/redeem`，攻击者必须**同时**持有：

- 一把只存在于应用服务器上的密钥（**hiddenKey**）
- 一个只发送给特定那一个浏览器的引用（**exposureKey**）
- 一份只有 Sudomimus 在用户通过真实挑战后才会签发的证明（**confirmationKey**）

任何单一的失误都凑不齐这三样东西。URL 泄露不会影响服务器的密钥；服务器被攻破不会牵连其他用户的会话；用户被钓鱼也不会拖累服务器。

机制细节在 [三密钥模型](/zh-cn/connect/three-key-model/) 里讲清楚了；但更通用的原则是：**把一份证明拆成三份，分散到三个独立的信任域里**。

> **不采用的方案：** 使用单个会话令牌承载全部兑换权限。

## 这对你意味着什么

接入 Sudomimus 的应用具有以下安全特性：

- 你永远见不到密码，所以也不需要安全地保存任何密码。
- 应用获得不透明、按应用区分的标识符，而不是可用于跨站关联的全局标识符。
- 你手里只会有用户同意分享给你的那些身份声明。
- 你的应用本地验证令牌；Sudomimus 即便宕机也不会把已登录用户挡在你的后端之外。
- 单个会话的 `hiddenKey` 独自泄露时，影响范围限于对应的 Connect 会话；长期应用凭据仍需妥善保管并及时轮换。
- 你不需要自己写账户锁定逻辑。
- 应用自有端点仍需采用常规的枚举与滥用防护；Sudomimus 会在自身协议边界保护邮箱发现流程。

接下来可以[选择接入方式](/zh-cn/getting-started/choose-integration/)，或直接查看具体的 [Connect 流程](/zh-cn/connect/flow/)。