跳转到内容

身份声明与共享

查看 Markdown

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

  • 声明策略(claim policy) —— 由开发者按应用设置:应用请求哪些声明,以及请求得有多强。
  • 声明授权(claim grant) —— 由用户按应用设置:他们同意共享哪些声明。

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

With 门户里你的应用详情页上,你把三条声明各设为以下之一:

策略含义
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 声明令牌的登录,原因有两种:

  • ClaimConsentRequired —— 用户没有授权某条 required 声明。
  • RequiredClaimDataMissing —— 用户已经授权了它,但账户缺少底层数据(例如纯 Steam 账户没有邮箱)。

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

用户如何解除阻塞,取决于客户端类型:

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

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

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

{
"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 —— emailAddressfirstNamelastName 各自只有在对应声明被共享时才出现。
  • OIDC id_token / /userinfo —— 由 scope 授权共同把关:邮箱声明变成 email(外加 email_verified);变成 given_name变成 family_namename 由已授权的部分组合而成。Synthetic 邮箱会以 email_verified: false 发出 —— 它是一个代理地址、不是已验证的邮箱,请不要当成已验证来处理。

这些字段在令牌 payload 中的位置见令牌格式

  • 开发者 —— 你的应用在 With 门户的详情页:把每条声明设为 Off / Optional / Required / Synthetic。
  • 用户 —— 账户门户里的数据共享视图:查看并撤销每个应用收到的内容。