---
title: 身份声明与共享
description: Sudomimus 如何把用户的邮箱、名、姓共享给应用 —— 开发者设置的声明策略、用户控制的授权，以及 scope 如何决定最终进入令牌的内容。
editUrl: true
head: []
template: doc
sidebar:
  order: 5
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

除了一个稳定的标识符之外，应用往往还想要一点点资料 —— 用户的**邮箱**、**名**或**姓**。Sudomimus 把这其中每一项都当作一条**声明（claim）**，只有在双方都明确同意时才共享。三条声明各自独立把关，共享分为两半：

- **声明策略（claim policy）** —— 由*开发者*按应用设置：应用请求哪些声明，以及请求得有多强。
- **声明授权（claim grant）** —— 由*用户*按应用设置：他们同意共享哪些声明。

只有当策略请求了某条声明**并且**用户授权了它，这条声明才会进入令牌。对于 OIDC，还必须请求了相应的 scope。控制权在用户手里，被撤销的声明会在下一个令牌起就不再共享。

## 声明策略（开发者）

在 [With 门户](https://with.sudomimus.com)里你的应用详情页上，你把三条声明各设为以下之一：

| 策略 | 含义 |
|---|---|
| **Off** | 从不请求。无论用户是否愿意，这条声明永不共享。 |
| **Optional** | 会请求，但用户可以拒绝。一旦拒绝，应用就直接收不到它。 |
| **Required** | 应用需要它。用户必须授权才能完成登录。 |
| **Synthetic** | 始终提供 —— 但值可能是生成的占位。用户看到的样子和 Optional 一样；如果他们拒绝（或账户没有真实数据），应用收到的是一个替身（占位姓名、一个 `…@proxy.sudomimus.email` 代理地址）而不是什么都收不到。它从不阻塞登录，也从不触发 errand。 |

在你配置策略之前创建的应用**什么都不请求** —— 不存在静默回填。你对每条声明都是主动选择加入的。

## 用户的授权

用户首次登录一个会请求声明的应用时，Sudomimus 会显示一个**同意（consent）界面**：

- **Required** 声明以锁定为开的形式展示 —— 授权它们是完成登录的一部分。
- **Optional** 声明以复选框展示，**默认不勾选** —— 由用户主动选择加入。
- **Synthetic** 声明同样以可选复选框展示，差别在文案里点明：不勾选它，应用收到的是一个占位值，而不是什么都收不到。

他们的决定会被记为三种状态之一 —— **已授权**、**已拒绝**或**尚未决定** —— 所以被拒绝的可选声明不会被反复打扰，而从未见过的声明会在下一次交互式登录时再问。

用户可以在账户门户的**数据共享**视图里查看并修改每一个决定：它列出每个共享声明的应用、当前每条声明是否在共享、以及应用是否要求它，并为每个应用提供一个**撤销**操作。由于授权在每次签发令牌时都是实时读取的，撤销会立即生效 —— 下一个令牌就不再包含该声明。

## 声明何时真正出现在令牌里

对某条声明而言，规则是：

> 策略**不是 Off**，**并且**用户已**授权**它 —— **并且**，对 OIDC 而言，请求了相应的 scope。

OIDC 的 scope 把关映射如下：

- `email` scope → **邮箱**声明
- `profile` scope → **名**与**姓**

非 OIDC 流程（Connect redeem/refresh、原生 direct-issue）没有 scope 把关 —— 仅由策略 + 授权决定。

**Synthetic** 声明是「要么授权、要么省略」之外的例外：它*始终*在场。用户授权了真实数据时令牌带真实数据；否则带一个稳定的、按应用区分的占位值（生成的姓名、一个 `…@proxy.sudomimus.email` 代理地址）。所以合成声明从不阻塞登录、也从不被省略 —— 只是对 OIDC 邮箱而言，应用会被告知该地址未经验证。

## Required 声明与非交互式登录

**Required** 声明只能通过交互式授权满足。对于邮箱等数据型声明，账户还必须*确实拥有*相应数据。因此，**非交互式**签发点会拒绝任何可能生成缺少 required 声明令牌的登录，原因有两种：

- `ClaimConsentRequired` —— 用户没有授权某条 required 声明。
- `RequiredClaimDataMissing` —— 用户*已经*授权了它，但账户缺少底层数据（例如纯 Steam 账户没有邮箱）。

这项检查保证 required 声明在签发的令牌中**一定存在**，绝不会被静默省略。它适用于原生 direct-issue、令牌刷新和 OIDC token 端点。

用户如何解除阻塞，取决于客户端类型：

- **原生客户端**（Steam / AccessKey direct-issue）没有*对应用本身*的交互式登录，所以 `403` 会带上一个 **[Errand](/zh-cn/native/claims-and-errand/)** —— 一段浏览器侧行，用户在那里登录（如果要写入数据）、补全缺失的数据、并授予同意。随后客户端重试。对 [AccessKey](/zh-cn/native/overview/) 而言，同样的同意也可以提前收集 —— 就在用户于门户里创建该 key 的那一刻。
- **浏览器 / OIDC 客户端**在下一次对该应用的常规交互式登录时解除阻塞，同意界面会内联展示。

未授权的**可选**声明则永远不会阻塞任何东西 —— 它只是被省略。**Synthetic** 声明同样从不阻塞 —— 它回退到一个占位值，所以它是「保证某个值一定在场」而又不强迫用户走一趟浏览器侧行的办法。

## `claims` 块

每当 Sudomimus 通过 Connect 或原生 direct-issue 签发或刷新令牌（`/redeem`、`/refresh`、`/direct-issue/*`）时，响应都会在令牌之外附带一个顶层 `claims` 块。它回答了缺失声明本来留下的疑问：这条声明*为什么*不在我的令牌里？

```json
{
  "email":     { "requirement": "REQUIRED", "state": "GRANTED" },
  "firstName": { "requirement": "OPTIONAL", "state": "DENIED"  },
  "lastName":  { "requirement": "OFF",      "state": "UNKNOWN" }
}
```

三条声明各给出它的 `requirement`（开发者的策略：`OFF` / `OPTIONAL` / `REQUIRED` / `SYNTHETIC`）与它的 `state`（用户的当前决定：`UNKNOWN` / `GRANTED` / `DENIED`）的组合。`UNKNOWN`（「从未问过」）与 `DENIED`（「明确拒绝」）之所以要区分，正是这里用三种状态而不是一个可空布尔值的原因。

结合[上面的纳入规则](#声明何时真正出现在令牌里)，这个块可以解释声明为何出现或缺失：策略为 `OFF`、用户从未作出选择、用户拒绝授权，或已经授权但账户缺少数据。direct-issue 因声明检查返回 `403` 时，同一个块会列出签发令牌前仍需满足的条件。

## 应用收到什么

- **Connect / 原生 access token** —— `emailAddress`、`firstName`、`lastName` 各自只有在对应声明被共享时才出现。
- **OIDC `id_token` / `/userinfo`** —— 由 scope **与**授权共同把关：**邮箱**声明变成 `email`（外加 `email_verified`）；**名**变成 `given_name`；**姓**变成 `family_name`；`name` 由已授权的部分组合而成。**Synthetic** 邮箱会以 `email_verified: false` 发出 —— 它是一个代理地址、不是已验证的邮箱，请不要当成已验证来处理。

这些字段在令牌 payload 中的位置见[令牌格式](/zh-cn/concepts/tokens-and-verification/)。

## 在哪里管理

- **开发者** —— 你的应用在 [With 门户](https://with.sudomimus.com)的详情页：把每条声明设为 Off / Optional / Required / Synthetic。
- **用户** —— 账户门户里的**数据共享**视图：查看并撤销每个应用收到的内容。