令牌与验证
Sudomimus 签发三种令牌,由不同的密钥签名、通过不同的机制验证。本页是令牌验证与字段内容的唯一参考 —— 每个 claim 的含义,以及决定字段取值的规则。
| 令牌 | 签发者 | 签名密钥 | 验证方式 | 携带的内容 |
|---|---|---|---|---|
| Access token | Connect(/redeem、/refresh、/direct-issue/*、/token) | 应用的 token-signing 私钥 | POST /info → applicationPublicKey | subject、firstName、lastName?、emailAddress? |
| Refresh token | Connect(/redeem、/direct-issue/*、/token) | 应用的 token-signing 私钥 | 与 access token 相同 | subject |
| OIDC ID token | OIDC(/token) | 平台级 OIDC 签名密钥 | oidc.sudomimus.com/.well-known/jwks.json | sub、iss、aud、exp、iat、nonce?、auth_time?、email?、name? |
三者都是用 RS256(RSA-2048)签名的 JWT。
Access token 与 refresh token
Section titled “Access token 与 refresh token”这是你的应用后端日常打交道的两种令牌。两者都由 Sudomimus 用与你应用绑定的密钥对签发 —— 每个应用都有自己独立的一对,轮换一个应用的密钥不会影响其他应用。
应用使用自己的 token-signing 公钥 验签,这把公钥通过 POST /info 拉取。推荐流程:
- 启动时调用
POST /info传入自己的applicationAnchor,缓存返回的applicationPublicKey。 - 每次接收到请求时,解析 JWT,检查 header 里的
kty === "Access"(refresh token 是"Refresh"),校验exp,并用缓存的公钥验签。 - 在开发者门户中轮换应用密钥之后,清空缓存并重新拉取。
官方 SDK 已经把这些事做好了 —— 详见 SDK 文档 中的 verifyAccessToken。
Connect 令牌没有 JWKS。 验证是 per-application 的,这是设计上有意为之:应用只需要知道一把和自己 applicationAnchor 绑定的公钥;任意一个应用的密钥泄露也不会影响其他应用。
kty 声明
Section titled “kty 声明”Sudomimus 的 access token / refresh token JWT 在 header 里带一个自定义 kty 字段,用来明确令牌类型:
- Access token:
kty: "Access"(大小写敏感) - Refresh token:
kty: "Refresh"(大小写敏感)
如果你在做 access token 校验时碰到 kty: "Refresh"(反之亦然),应当直接拒绝 —— 官方 SDK 已经做了这件事,但如果你手动验签,这是最容易漏掉的检查之一。
TTL 区间
Section titled “TTL 区间”| 默认值 | 最小值 | 最大值 | |
|---|---|---|---|
| Access token | 3 小时 (10800s) | 60 秒 | 7 天 (604800s) |
| Refresh token | 30 天 (2592000s) | 1 天 (86400s) | 365 天 (31536000s) |
规则上和 inquiry 上的 TTL 覆盖都受这套区间限制。当多个 TTL 同时命中(例如一条 Layer 1 规则 + 一条 Layer 3 inquiry 约束),Sudomimus 把它们 fold 成最小值;如有必要,refresh TTL 会被提升到不小于 access TTL。
Access token 字段
Section titled “Access token 字段”Sudomimus 的 access token 与 refresh token 把所有标准 JWT claim(kty、iss、aud、sub、iat、exp)放在 JWT header 中,而 body 只放应用专属字段(subject、firstName、lastName、emailAddress)。如果你手动验证,请从 header 而非 payload 读取 iss / aud / exp —— 官方 SDK 会替你处理这点。
// JWT header{ "alg": "RS256", "kty": "Access", "iss": "sudomimus.com", "aud": "<applicationAnchor>", "sub": "<refreshTokenIdentifier>", "iat": <epoch>, "exp": <epoch>}// JWT body{ "subject": "<sector subject>", "firstName": "<string>", "lastName": "<string,可选>", "emailAddress": "<string,可选>"}| Claim | 含义 |
|---|---|
kty | 总是 "Access"。任何不匹配的令牌都应拒绝。 |
subject | 应用可见的扇区主体(sector subject),即按(账户 × 扇区)生成的不透明标识符(例如 sub_9SQ5535CRWNDDM2T)。应用应使用该值标识用户。它在同一扇区内对同一用户保持稳定,但可以由用户轮换,并且在不同扇区之间互不相同。请将其视为不透明值,不要解析。原始账户 UUID 永远不会写入令牌。 |
firstName、lastName | 用户的显示名称。lastName 可选。 |
emailAddress | 与本次登录关联的已验证邮箱。选取规则见下文。当账户无任何已验证邮箱时(例如纯 Steam 或纯 AccessKey 账户)省略。 |
iss | 总是 "sudomimus.com"。 |
aud | 签发本令牌的应用的 applicationAnchor。 |
sub | 签发本 access token 的 refresh token 的标识符。用于吊销关联,不是用户标识。 |
iat、exp | 标准 JWT 签发时间 / 过期时间,单位秒(epoch)。 |
Refresh token 字段
Section titled “Refresh token 字段”同样的 header / body 划分也适用 —— 标准 claim 在 header,扇区主体在 body。
// JWT header{ "alg": "RS256", "kty": "Refresh", "iss": "sudomimus.com", "aud": "<applicationAnchor>", "iat": <epoch>, "exp": <epoch>}// JWT body{ "subject": "<sector subject>"}这里的 subject 与 access token 上携带的扇区主体相同。它仅供参考 —— Sudomimus 通过 refresh token 的内部句柄来识别它,而非读取此 body —— 所以应用不应以它作为吊销或存储的键。
Refresh token body 故意不包含用户显示名和邮箱 —— 这些字段在每次签发新 access token 时都会从账户行重新读取,确保用户更新资料后 access token 上的值保持最新。
emailAddress 字段的选取规则
Section titled “emailAddress 字段的选取规则”Access token 上的 emailAddress 在签发时按一条确定性规则选取:
- 如果用户用 email-OTP 登录,字段就是他输入并通过验证码证明持有的那封邮箱。
- 否则 —— passkey、Steam ticket、AccessKey 或其他任何登录方式 —— 字段是该账户在
EmailIdentity表中的主要邮箱。 - 如果账户根本没有任何已验证邮箱(例如纯 Steam 账户未挂邮箱),字段直接省略。你的验签代码应当把
emailAddress视为可选。
「主要邮箱」默认是账户最先获得的那封已验证邮箱,用户也可以在 With 门户的登录方式页面中改选。Access token 一旦签发,通过 POST /refresh 续期时会保留原本的 emailAddress 值,所以应用可以在一次会话内安全地缓存它。
OIDC ID token
Section titled “OIDC ID token”当应用作为 OIDC 接入方 集成时,/token 端点会在 access_token 之外再返回一个 id_token。这个 ID token 由平台级 OIDC 签名密钥签发(不是应用专属密钥),通过 https://oidc.sudomimus.com/.well-known/jwks.json 上的 JWKS 验证。
ID token 字段遵循 OpenID Connect 标准:
{ "iss": "https://oidc.sudomimus.com", "sub": "<sector subject>", "aud": "<client_id>", "exp": <epoch>, "iat": <epoch>, "nonce": "<来自 /authorize(如有)>", "auth_time": <epoch(如有)>, "email": "<授予 'email' scope 时存在>", "name": "<授予 'profile' scope 时存在>"}| Claim | 含义 |
|---|---|
iss | 总是 "https://oidc.sudomimus.com"。 |
sub | 扇区主体 —— 与配对的 access token 上 subject 字段相同的(账户 × 扇区)值,不是原始账户 UUID。在你所属扇区内对同一用户稳定,可轮换,且不同扇区之间不同。 |
aud | 接入方的 OIDC client_id。 |
exp、iat | 标准 JWT 时间字段,单位秒(epoch)。 |
nonce | 首次签发时回显接入方在 /authorize 时传入的值。按 OIDC core 1.0 §12.1,refresh-token grant 不会再回显。 |
auth_time | 用户实际完成认证的时间(秒)。refresh-token grant 会保留原值。 |
email | 用户邮箱 —— 与配对的 access token 上的值相同。仅在授予了 email scope 时存在。 |
name | 用户全名,由 firstName lastName 拼成。仅在授予了 profile scope 时存在。 |
OIDC 签名密钥会定期轮换;JWKS 在轮换期间会同时发布当前生效和最近退役的两把密钥,保证验签连续可用。
OIDC 流程下的 access token
Section titled “OIDC 流程下的 access token”OIDC /token 同时也返回一个 access_token,形状与上面的 Connect access token 完全相同 —— 同样的 kty、同样的 per-application 签名密钥、同样的字段集合。区别只在签发路径;签发出来后,你的应用以完全相同的方式(POST /info → 缓存公钥)验签。
完整接入方流程(含 /userinfo 和 /end-session)见 OIDC 接入方。
- Connect 协议(access / refresh token 通过
POST /info验签):你的应用后端直接和 Sudomimus 对接。最低开销,无额外跳转。 - OIDC(ID token 通过 JWKS 验签):你的应用已经接了某个标准 OIDC 库,或者你需要让第三方系统作为接入方。Sudomimus 此时扮演 IdP。
同一个应用通常只用其中一种 —— 没必要两种都接。