跳转到内容

令牌与验证

查看 Markdown

Sudomimus 签发三种令牌,由不同的密钥签名、通过不同的机制验证。本页是令牌验证与字段内容的唯一参考 —— 每个 claim 的含义,以及决定字段取值的规则。

令牌签发者签名密钥验证方式携带的内容
Access tokenConnect(/redeem/refresh/direct-issue/*/token应用的 token-signing 私钥POST /infoapplicationPublicKeysubjectfirstNamelastName?emailAddress?
Refresh tokenConnect(/redeem/direct-issue/*/token应用的 token-signing 私钥与 access token 相同subject
OIDC ID tokenOIDC(/token平台级 OIDC 签名密钥oidc.sudomimus.com/.well-known/jwks.jsonsubissaudexpiatnonce?auth_time?email?name?

三者都是用 RS256(RSA-2048)签名的 JWT。

这是你的应用后端日常打交道的两种令牌。两者都由 Sudomimus 用与你应用绑定的密钥对签发 —— 每个应用都有自己独立的一对,轮换一个应用的密钥不会影响其他应用。

应用使用自己的 token-signing 公钥 验签,这把公钥通过 POST /info 拉取。推荐流程:

  1. 启动时调用 POST /info 传入自己的 applicationAnchor,缓存返回的 applicationPublicKey
  2. 每次接收到请求时,解析 JWT,检查 header 里的 kty === "Access"(refresh token 是 "Refresh"),校验 exp,并用缓存的公钥验签。
  3. 在开发者门户中轮换应用密钥之后,清空缓存并重新拉取。

官方 SDK 已经把这些事做好了 —— 详见 SDK 文档 中的 verifyAccessToken

Connect 令牌没有 JWKS。 验证是 per-application 的,这是设计上有意为之:应用只需要知道一把和自己 applicationAnchor 绑定的公钥;任意一个应用的密钥泄露也不会影响其他应用。

Sudomimus 的 access token / refresh token JWT 在 header 里带一个自定义 kty 字段,用来明确令牌类型:

  • Access token:kty: "Access"(大小写敏感)
  • Refresh token:kty: "Refresh"(大小写敏感)

如果你在做 access token 校验时碰到 kty: "Refresh"(反之亦然),应当直接拒绝 —— 官方 SDK 已经做了这件事,但如果你手动验签,这是最容易漏掉的检查之一。

默认值最小值最大值
Access token3 小时 (10800s)60 秒7 天 (604800s)
Refresh token30 天 (2592000s)1 天 (86400s)365 天 (31536000s)

规则上和 inquiry 上的 TTL 覆盖都受这套区间限制。当多个 TTL 同时命中(例如一条 Layer 1 规则 + 一条 Layer 3 inquiry 约束),Sudomimus 把它们 fold 成最小值;如有必要,refresh TTL 会被提升到不小于 access TTL。

Sudomimus 的 access token 与 refresh token 把所有标准 JWT claimktyissaudsubiatexp)放在 JWT header 中,而 body 只放应用专属字段subjectfirstNamelastNameemailAddress)。如果你手动验证,请从 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 永远不会写入令牌。
firstNamelastName用户的显示名称。lastName 可选。
emailAddress与本次登录关联的已验证邮箱。选取规则见下文。当账户无任何已验证邮箱时(例如纯 Steam 或纯 AccessKey 账户)省略。
iss总是 "sudomimus.com"
aud签发本令牌的应用的 applicationAnchor
sub签发本 access token 的 refresh token 的标识符。用于吊销关联,不是用户标识。
iatexp标准 JWT 签发时间 / 过期时间,单位秒(epoch)。

同样的 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 上的值保持最新。

Access token 上的 emailAddress 在签发时按一条确定性规则选取:

  1. 如果用户用 email-OTP 登录,字段就是他输入并通过验证码证明持有的那封邮箱。
  2. 否则 —— passkey、Steam ticket、AccessKey 或其他任何登录方式 —— 字段是该账户在 EmailIdentity 表中的主要邮箱
  3. 如果账户根本没有任何已验证邮箱(例如纯 Steam 账户未挂邮箱),字段直接省略。你的验签代码应当把 emailAddress 视为可选。

「主要邮箱」默认是账户最先获得的那封已验证邮箱,用户也可以在 With 门户的登录方式页面中改选。Access token 一旦签发,通过 POST /refresh 续期时会保留原本的 emailAddress 值,所以应用可以在一次会话内安全地缓存它。

当应用作为 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
expiat标准 JWT 时间字段,单位秒(epoch)。
nonce首次签发时回显接入方在 /authorize 时传入的值。按 OIDC core 1.0 §12.1refresh-token grant 不会再回显。
auth_time用户实际完成认证的时间(秒)。refresh-token grant 会保留原值。
email用户邮箱 —— 与配对的 access token 上的值相同。仅在授予了 email scope 时存在。
name用户全名,由 firstName lastName 拼成。仅在授予了 profile scope 时存在。

OIDC 签名密钥会定期轮换;JWKS 在轮换期间会同时发布当前生效和最近退役的两把密钥,保证验签连续可用。

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。

同一个应用通常只用其中一种 —— 没必要两种都接。