跳转到内容

Layer 2 — 身份准入规则

查看 Markdown

身份准入规则控制应用允许哪些身份完成登录。它在用户成功证明身份之后、认证请求被标记为 realized 之前运行。

将它作为独立的一层,是因为“确认用户拥有 [email protected]”与“决定应用是否接受 [email protected]”是两个不同的问题。前者由认证规则处理,后者由身份准入规则处理。

身份准入规则支持五种 constraint type,同一应用可以混合使用。层内规则按 OR 组合,任意一条匹配即可放行。

TypePayload匹配语义
EMAIL{ "allowedEmails": string[] }大小写不敏感的 glob,仅 * 特殊
STEAM_ID{ "allowedSteamIds": (string | "*")[] }十进制 SteamID64 字符串精确匹配,或字面量 "*" 通配
ACCOUNT_ALIAS{ "allowedAccountAliases": string[] }对 realize 时账户的账户别名(用户可见、应用不可见、可轮换的句柄)做精确匹配 —— 没有通配
SECTOR_SUBJECT{ "allowedSectorSubjects": string[] }对 realize 时账户在该应用所属扇区(Sector)下的扇区主体(sector subject)(应用可见的令牌 sub)做精确匹配 —— 没有通配
EVERYONE{}无条件放行:匹配每个已通过认证的账户,包括没有任何已验证邮箱的账户。payload 为空。

匹配对象都来自当前正在进行准入判定的账户或会话:

  • EMAIL 对应账户持有的所有已验证邮箱,即账户的全部 EmailIdentity 记录,而不只是本次登录使用的邮箱。只要其中任意一个匹配允许列表即可通过。使用邮箱 OTP 注册时账户尚不存在,此时匹配正在注册的邮箱。
  • STEAM_ID 对应两条 Steam 路径验证后得到的 SteamID64 —— 既包括游戏端内通过 Steamworks 的 STEAM_TICKET direct-issue,也包括浏览器端的 STEAM_OPENID “Sign in with Steam”。两条路径解析到同一份 Steam 身份。从未关联邮箱的 Steam-first 账户必须依赖 STEAM_ID(或 ACCOUNT_ALIAS / SECTOR_SUBJECT)规则才能通过;只有 EMAIL 规则的 Layer 2 会拒绝它们。
  • ACCOUNT_ALIAS 对应账户的账户别名 —— 用户在 With 门户中看到并自行管理的、可轮换的句柄(例如 quiet-meadow-7h2k-9m4p-3fnp-falcon)。它从不向应用披露,因此适合用来锁定某个具体的人,无论其通过哪个应用 realize。
  • SECTOR_SUBJECT 对应账户的扇区主体,即按(账户 × 扇区)生成的不透明标识符,也是应用在令牌中实际收到的 sub(例如 sub_9SQ5535CRWNDDM2T)。共享同一扇区的应用会为同一用户看到相同值,不同扇区则会看到不同值。它可以用于将应用已经收到的特定用户标识加入允许列表。

ACCOUNT_ALIASSECTOR_SUBJECT 都是不透明值,只进行精确匹配,不检查格式,也不支持通配。两者都要在账户及其扇区主体存在后才会生成,因此新注册产生的值不可能预先出现在允许列表中。Layer 2 如果只包含这两类规则,就不会接受新注册,只能允许已有账户。两个值都可以轮换;轮换后,引用旧值的允许列表将不再匹配。

allowedEmails 中的每一项以大小写不敏感的方式匹配;只有 * 有特殊含义(匹配零个或多个字符)。其它字符(包括 @.+)都是字面量。

Pattern匹配
[email protected]精确匹配
*@example.comexample.com 域下的任意邮箱
alice+*@example.com[email protected] 的任意 plus 变体
*任意邮箱

比较前会对输入做 trim 和 lowercase。

每一项要么是字面量 "*"(匹配任意已验证的 SteamID64),要么是十进制 SteamID64 字符串(大小写敏感精确匹配)。写入时强制 [0-9]{1,20} 的形状。

{
"constraintType": "STEAM_ID",
"payload": {
"allowedSteamIds": ["76561198000000000", "76561198000000001"]
}
}

每一项是目标账户账户别名的精确字面量 —— 即可轮换、面向用户的句柄(例如 quiet-meadow-7h2k-9m4p-3fnp-falcon)。没有通配,也没有 glob;该值不透明,按字面比对。

{
"constraintType": "ACCOUNT_ALIAS",
"payload": {
"allowedAccountAliases": [
"quiet-meadow-7h2k-9m4p-3fnp-falcon",
"bold-harbor-2x4q-8m1p-5kna-otter"
]
}
}

账户别名是用户在 With 门户中管理的句柄,不会向应用披露。它适合用于允许某位特定用户,而不依赖该用户通过哪个应用登录。用户轮换别名后,引用旧值的允许列表将不再匹配。

每一项是目标账户在该应用所属扇区下扇区主体的精确字面量 —— 即应用可见的令牌 sub(例如 sub_9SQ5535CRWNDDM2T)。没有通配,也没有 glob;该值不透明,按字面比对。

{
"constraintType": "SECTOR_SUBJECT",
"payload": {
"allowedSectorSubjects": [
"sub_9SQ5535CRWNDDM2T",
"sub_4K2P8M1N7QRWXY3Z"
]
}
}

扇区主体是应用及同一扇区内其他应用实际收到的用户标识符;不同扇区会为同一用户看到不同值。你可以将应用已经用于标识用户的值加入允许列表。用户轮换扇区主体后,引用旧值的规则将不再匹配。

两者都是不支持通配的精确允许列表。如果流程还需要接受新用户注册,请同时配置 EMAILSTEAM_ID 规则。

EVERYONE 是显式的”本应用对任何人开放”规则。它携带空 payload,无条件匹配每一个已通过鉴权的账户。

{
"constraintType": "EVERYONE",
"payload": {}
}

这不会削弱“允许列表 + 默认拒绝”模型,因为 EVERYONE 本身就是一项显式配置。没有 Layer 2 规则的应用仍会拒绝所有人;EVERYONE 表示任何通过 Layer 1 的账户都可以完成准入。它是唯一能够允许没有已验证邮箱账户的 Layer 2 类型,例如仅通过 Steam 或 Battle.net 登录的用户,因此适合不按身份设限的公开应用。由于层内采用 OR 语义,它与其他规则并列时会匹配所有账户,请谨慎使用。

每条 Layer 2 规则的外层结构相同,不同类型只有 constraintTypepayload 的内容不同。

{
"constraintType": "EMAIL",
"payload": { "allowedEmails": ["*@example.com"] },
"accessTokenTtlSeconds": null,
"refreshTokenTtlSeconds": null
}

应用上的多条规则之间是 OR:身份匹配任一条规则即可通过。

/establish 中的 realizeConstraints 使用相同结构,用于限制单次认证请求允许的身份:

{
"applicationAnchor": "my-app",
"realizeConstraints": [
{
"constraintType": "EMAIL",
"payload": { "allowedEmails": ["[email protected]"] }
}
]
}
  • 字段缺失 → 不收紧;只看应用规则。
  • 字段存在且为空数组 → 拒绝。
  • 字段存在且非空 → 与应用规则做 AND。约束内部的 payload 列表本身也必须非空。

应用配置了一条 EMAIL 身份准入规则,内容为 allowedEmails: ["*@example.com"]。管理员入口在一次认证请求中传入 realizeConstraints: [{ constraintType: "EMAIL", payload: { allowedEmails: ["[email protected]"] } }]

用户认证身份应用允许?Inquiry 允许?结果
[email protected]是(命中 *@example.com通过
[email protected]是(命中 *@example.com认证后被拒绝
[email protected]不适用认证后被拒绝

身份准入规则在认证规则之后执行。此时用户已经证明自己拥有该邮箱;身份准入规则只决定应用是否接受这个身份完成当前登录。