OIDC 流程
如果应用已经支持 OpenID Connect,或者你希望直接使用现有 OIDC 库,可以将 Sudomimus 作为标准 OIDC 提供方接入。Sudomimus 的 OIDC provider 部署在 oidc.sudomimus.com,支持带 PKCE 的授权码流程。你的应用在该流程中是接入方(Relying Party,RP)。
适用场景:
- 你的框架或平台原生支持 OIDC(Next-Auth、Spring Security、Keycloak adapter 等),希望直接把 Sudomimus 接入做 IdP。
- 你要和某个已经只接 OIDC 的伙伴系统对接。
- 你更熟悉 OIDC 的 client、scope 和 ID token 概念,不希望使用 Connect 协议。
如果你从零开始、只想做最小的自定义集成,Connect 协议 通常更短。
官方 SDK 主要覆盖 Connect、Session、Device、Native 与 token 验证辅助。如果你的 OIDC 应用还需要验证 Sudomimus 应用 access token,或直接管理 ApplicationSession,请参考 SDK。
Discovery
Section titled “Discovery”Sudomimus 发布标准 OIDC discovery 文档。将库的 issuer 设置为 https://oidc.sudomimus.com 后,客户端库可以自动读取其余配置:
curl https://oidc.sudomimus.com/.well-known/openid-configurationSudomimus 不另外发布 OIDC OpenAPI schema。请通过 discovery 获取当前部署的 端点 URL 与已声明能力,通过 OpenID Connect 和 OAuth 标准理解协议语义,并以 本指南确认 Sudomimus 支持的 profile 与平台约束。生成的 OpenAPI 参考用于 Connect、Session、Native 和 Device 产品 API。
discovery 文档声明:
response_types_supported:["code"](仅 authorization code flow)。authorization_response_iss_parameter_supported:true—— 授权回调按照 RFC 9207 标识提供方。grant_types_supported:["authorization_code", "refresh_token"]。scopes_supported:["openid", "email", "profile", "offline_access"]。claims_supported包含sub、email、email_verified、name、given_name、family_name、picture和picture_animated。claim_state_endpoint指向 Sudomimus 提供方专属的实时 claim 策略与同意状态端点。id_token_signing_alg_values_supported:["RS256"]。code_challenge_methods_supported:["S256"]—— 强制 PKCE,不支持 plain。token_endpoint_auth_methods_supported:["private_key_jwt", "client_secret_basic", "client_secret_post", "none"]。ui_locales_supported:["en-US", "zh-CN"]。
在 with.sudomimus.com 中,为需要启用 OIDC 的应用完成以下配置:
-
添加一条 Layer 3 OIDC 返回规则:
{"returnMethod": "OIDC","payload": {"redirectUris": ["https://app.example.com/oidc/callback"],"postLogoutRedirectUris": ["https://app.example.com/"],"allowedScopes": ["openid", "email", "profile", "offline_access"],"tokenEndpointAuthMethod": "private_key_jwt"}} -
与其他应用一样配置 Layer 1 和 Layer 2:至少添加一条认证规则(例如
PASSKEY_USERNAMELESS或PASSKEY_REASONED)和一条身份准入规则(例如允许特定邮箱的EMAIL规则)。OIDC 流程仍使用平台的同一套用户认证挑战。 -
选择客户端认证方式:
private_key_jwt(推荐用于机密客户端)—— RP 持有私钥,在/token上用 JWT assertion 做客户端认证。签名密钥就是应用的 client-auth 私钥;机密 Connect 集成调用/establish时也使用这把密钥。client_secret_basic(机密客户端)—— RP 在/token的 HTTPAuthorization: Basic头中携带共享密钥。client_secret_post(机密客户端)—— RP 在/token的表单体中发送共享密钥(client_id+client_secret参数)。none—— 仅限公开客户端(SPA、无后端的移动应用)。必须使用 PKCE。
client_secret_basic 和 client_secret_post 共用同一把应用密钥。请在 With 门户的应用页面生成或轮换它,并妥善保存。
应用的 applicationAnchor 就是你的 client_id。
OIDC 流程
Section titled “OIDC 流程”1. 授权请求
Section titled “1. 授权请求”把用户浏览器跳转到 /authorize,附带标准 OIDC 参数:
https://oidc.sudomimus.com/authorize ?client_id=my-app &redirect_uri=https%3A%2F%2Fapp.example.com%2Foidc%2Fcallback &response_type=code &scope=openid%20email%20profile &ui_locales=zh-CN%20en-US &state=<csrf-token> &nonce=<random-nonce> &code_challenge=<S256-of-verifier> &code_challenge_method=S256必填:client_id、redirect_uri、response_type=code、scope(必须包含 openid)、code_challenge、code_challenge_method=S256。
选填(建议带上):state、nonce。
如果应用已知用户偏好的界面语言,可以传入可选的 ui_locales。请按偏好顺序填写以空格分隔的 BCP 47 语言标签。Sudomimus 会采用第一个支持的值(en-US 或 zh-CN);不支持的值会被忽略,不会阻止登录。这个提示只作用于本次登录,用户仍可在页面中切换语言。
生成有效的 PKCE 参数
Section titled “生成有效的 PKCE 参数”建议让 OIDC 客户端库生成 PKCE 参数。每次发起授权都要生成一组新参数,并把 verifier 与待完成的登录状态一起保存到回调完成为止。
如果需要自行生成:
code_verifier长度必须为 43–128 个字符,只能使用英文字母、数字、-、.、_和~。code_challenge是 verifier 的 SHA-256 摘要经过无填充 base64url 编码后的结果。S256 challenge 固定为 43 个字符,只能使用英文字母、数字、-和_。/authorize只发送code_challenge;在/token兑换授权码时,发送原始且未经修改的code_verifier。
不要重复使用 verifier,也不要把文档里的固定示例用于生产环境。Sudomimus 会拒绝格式不正确的参数和不匹配的 verifier/challenge。
选择登录交互方式
Section titled “选择登录交互方式”多数应用可以不传 prompt 和 max_age,直接使用普通的交互式登录。只有在应用确实需要指定行为时才添加这些参数:
prompt=login要求重新认证。Sudomimus 的每次交互式 OIDC 请求本来都会执行一次全新认证。prompt=consent会再次展示授权确认,即使用户已经保存了长期的声明共享选择。prompt=none用于非交互式检查。Sudomimus 不保留可复用的 provider 浏览器会话,因此会返回login_required;应用应捕获该错误,再发起交互式授权请求。max_age是非负的秒数。由于每次交互式请求都会重新认证,任何有效的max_age都会得到满足。
只有当应用需要在用户离开或关闭应用后继续刷新令牌时,才请求 offline_access。Sudomimus 会向用户展示一个默认未选中的独立离线会话选项。如果用户拒绝,登录仍会成功,但返回的 scope 不包含 offline_access,也不会签发 refresh token。应用应以返回的 scope 和是否存在 refresh_token 为准。
Sudomimus 会将用户跳转到 via.sudomimus.com,完成 Layer 1 允许的认证方式。认证和身份准入检查成功后,浏览器会携带 code、原始 state 和 iss=https://oidc.sudomimus.com 返回 redirect_uri。
Sudomimus 验证注册回调地址后发生的授权错误,会携带 error、error_description、原始 state 和同一个 iss 返回该回调。OIDC 库必须把 iss 与本次登录保存的 issuer 做精确比较,并在缺失或不一致时于兑换授权码之前拒绝响应。回调地址验证之前的错误则直接以 JSON 返回。请把 server_error 视为暂时性的服务端故障,让用户稍后重试,并且不要缓存协议错误响应。
2. 令牌交换
Section titled “2. 令牌交换”向 /token 提交授权码。按照 OIDC 规范,请求体必须使用 application/x-www-form-urlencoded:
curl -X POST https://oidc.sudomimus.com/token \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=authorization_code" \ --data-urlencode "code=$AUTH_CODE" \ --data-urlencode "redirect_uri=https://app.example.com/oidc/callback" \ --data-urlencode "code_verifier=$PKCE_VERIFIER" \ --data-urlencode "client_id=my-app" \ --data-urlencode "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \ --data-urlencode "client_assertion=$CLIENT_ASSERTION_JWT"client_assertion 是使用应用 client-auth 私钥签名的 JWT。必需声明包括:iss = client_id、sub = client_id、aud = 完整且精确的 token endpoint URL(生产环境为 https://oidc.sudomimus.com/token)、全新的 jti、iat 和 exp(与 iat 相差不超过 300 秒)。签名算法为 RS256。
curl -X POST https://oidc.sudomimus.com/token \ -u "my-app:$CLIENT_SECRET" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=authorization_code" \ --data-urlencode "code=$AUTH_CODE" \ --data-urlencode "redirect_uri=https://app.example.com/oidc/callback" \ --data-urlencode "code_verifier=$PKCE_VERIFIER"Basic 用户名就是 client_id,因此无需在表单中重复发送 client_id。标准 OIDC 客户端库默认使用这种请求形式。
curl -X POST https://oidc.sudomimus.com/token \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=authorization_code" \ --data-urlencode "code=$AUTH_CODE" \ --data-urlencode "redirect_uri=https://app.example.com/oidc/callback" \ --data-urlencode "code_verifier=$PKCE_VERIFIER" \ --data-urlencode "client_id=my-app" \ --data-urlencode "client_secret=$CLIENT_SECRET"curl -X POST https://oidc.sudomimus.com/token \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=authorization_code" \ --data-urlencode "code=$AUTH_CODE" \ --data-urlencode "redirect_uri=https://app.example.com/oidc/callback" \ --data-urlencode "code_verifier=$PKCE_VERIFIER" \ --data-urlencode "client_id=my-app"不提交 client assertion。PKCE(code_verifier 与授权请求的 code_challenge 匹配)是唯一的客户端认证。
成功响应(JSON):
{ "access_token": "<JWT>", "token_type": "Bearer", "expires_in": 10800, "id_token": "<JWT>", "scope": "openid email profile"}id_token—— 由 Sudomimus 的平台级 OIDC 密钥签发,通过https://oidc.sudomimus.com/.well-known/jwks.json验证。它包含最小协议声明(iss、sub、aud、exp、iat、at_hash,以及可选的nonce、auth_time)和认证上下文(amr、acr)。个人资料从/userinfo获取。access_token—— 由应用的 token-signing 密钥签发,结构与 Connect 协议的访问令牌相同。refresh_token—— 仅在请求了offline_access且用户允许离线会话时返回。
3. Userinfo
Section titled “3. Userinfo”curl https://oidc.sudomimus.com/userinfo \ -H "Authorization: Bearer $ACCESS_TOKEN"返回 scope 允许的声明:
{ "sub": "<sector subject>", "email": "<只在授予 'email' scope 时返回>", "email_verified": true, "name": "<只在授予 'profile' scope 时返回>", "given_name": "<只在授予 'profile' scope 时返回>", "family_name": "<只在授予 'profile' scope 时返回>", "picture": "<只在授予 'profile' scope 时返回>", "picture_animated": "<只在授予 'profile' scope 时返回>"}sub 是 扇区主体(sector subject) —— 按扇区维度、应用可见的标识符,与 id_token 的 sub 相同。请用它作为用户键。/userinfo 同时支持 GET 与 POST。
picture 与私有 claim picture_animated 遵循扇区头像交付约定;见头像声明与交付。
Claim state
Section titled “Claim state”如果应用需要当前策略与同意状态,请携带同一个 Bearer access token 调用 discovery
中的 claim_state_endpoint。它同时支持 GET 与 POST,并且不会返回资料值:
{ "sub": "<sector subject>", "claims": { "email": { "requirement": "OPTIONAL", "state": "GRANTED" }, "given_name": { "requirement": "OPTIONAL", "state": "GRANTED" }, "family_name": { "requirement": "OFF", "state": "UNKNOWN" }, "picture": { "requirement": "OFF", "state": "UNKNOWN" }, "picture_animated": { "requirement": "OFF", "state": "UNKNOWN" } }}只返回 email scope 与 profile scope 覆盖的条目。只有 openid scope 时,
claims 是空对象。
每个 id_token 都包含:
| 声明 | 含义 |
|---|---|
amr | 标准 Authentication Methods References 值。例如 passkey 是 ["hwk", "user"],邮箱验证码是 ["otp"]。 |
acr | Sudomimus 的认证上下文字符串。例如 urn:sudomimus:acr:passkey 或 urn:sudomimus:acr:email-otp。 |
如果你的 RP 在敏感操作前需要确认用户刚才用了抗钓鱼方式登录,请检查 acr 是否为 urn:sudomimus:acr:passkey。Google、GitHub、Discord、Battle.net、X、Steam 和企业联合登录这类上游身份源都会映射到 amr: ["pwd"];需要区分具体来源时,请看 acr。
4. Refresh
Section titled “4. Refresh”如果请求了 offline_access、用户允许离线会话,并且响应中包含 refresh token,再到 /token 兑换:
curl -X POST https://oidc.sudomimus.com/token \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=refresh_token" \ --data-urlencode "refresh_token=$REFRESH_TOKEN" \ --data-urlencode "client_id=my-app" # 机密客户端再加 client_assertion刷新时应继续使用授权码兑换时选择的客户端认证方式。使用
client_secret_basic 时,通过 -u "my-app:$CLIENT_SECRET" 发送凭据,并省略
表单中的 client_id;使用 client_secret_post 时,在表单中同时发送
client_id 和 client_secret。
可以传入可选的 scope,请求原始授权范围的子集。根据 OIDC §12.1,刷新得到的 ID token 不会包含新的 nonce。
5. 结束会话
Section titled “5. 结束会话”https://oidc.sudomimus.com/end-session ?id_token_hint=<id_token> &client_id=my-app &post_logout_redirect_uri=https%3A%2F%2Fapp.example.com%2F &state=<可选>/end-session 通知 OIDC 会话结束,并把用户跳转到 post_logout_redirect_uri(必须与注册过的某个 URI 完全匹配)。如果 id_token_hint 验证通过,并且其中的 aud 与当前 client 匹配,Sudomimus 会推进账户/应用会话权威,并吊销该账户在这个应用下的旧 ApplicationSession。
伪造、无效或已经无法对应到账户的 hint 不会阻止跳转,只是不会触发应用会话吊销。hint 可以是过期的,这样 RP 即使本地会话已经超时,也仍然可以发起一次干净的 logout。
如果你手上有某一个 refresh token,只想吊销这一条会话,请调用 Session API /logout。如果后端需要在不依赖 id_token_hint 的情况下吊销某个账户在应用内的所有会话,请调用 /revoke-all。详见 管理会话。
使用 OIDC 库
Section titled “使用 OIDC 库”绝大多数现代 OIDC 库(Node 的 openid-client、Python 的 authlib、Java 的 nimbus-jose-jwt 等)都能从 issuer URL 自动发现,并自动处理 PKCE、JWKS、令牌验证。这个 Node 示例支持 openid-client 6.x(使用 6.8.4 验证),采用注册为 token_endpoint_auth_method: "none" 的公共 PKCE 客户端:
import * as oidc from "openid-client";
const redirectUri = "https://app.example.com/oidc/callback";const configuration = await oidc.discovery( new URL("https://oidc.sudomimus.com"), "my-app", { redirect_uris: [redirectUri], response_types: ["code"], token_endpoint_auth_method: "none", }, oidc.None(), { execute: [oidc.enableNonRepudiationChecks], },);
export const startSignIn = async (savePendingSignIn) => {
const codeVerifier = oidc.randomPKCECodeVerifier(); const codeChallenge = await oidc.calculatePKCECodeChallenge(codeVerifier); const state = oidc.randomState(); const nonce = oidc.randomNonce();
await savePendingSignIn({ codeVerifier, state, nonce, });
return oidc.buildAuthorizationUrl(configuration, { redirect_uri: redirectUri, scope: "openid email profile", code_challenge: codeChallenge, code_challenge_method: "S256", state, nonce, });};
export const finishSignIn = async ( callbackUrl, consumePendingSignIn, createServerSideApplicationSession,) => {
const pendingSignIn = await consumePendingSignIn(); if (pendingSignIn === undefined) { throw new Error("OIDC sign-in state is missing or expired"); }
const tokens = await oidc.authorizationCodeGrant( configuration, callbackUrl, { pkceCodeVerifier: pendingSignIn.codeVerifier, expectedState: pendingSignIn.state, expectedNonce: pendingSignIn.nonce, }, ); const claims = tokens.claims(); if (claims?.sub === undefined) { throw new Error("The validated ID token did not contain sub"); }
const userinfo = await oidc.fetchUserInfo( configuration, tokens.access_token, claims.sub, );
await createServerSideApplicationSession({ userKey: claims.sub, refreshToken: tokens.refresh_token, });
return userinfo;};savePendingSignIn 和 consumePendingSignIn 表示与浏览器本次登录绑定的短期服务端存储。回调时只能消费一次 verifier、state 和 nonce。authorizationCodeGrant 会验证授权响应中的 discovery RFC 9207 issuer 与预期 state,并验证 ID-token nonce;配置的 non-repudiation check 还会通过 discovery 获得的 JWKS 验证 ID-token 签名。全部验证成功后,claims() 才会提供这些 claims。
createServerSideApplicationSession 表示应用自己的会话存储。请使用验证后的、限定于应用的 sub 作为用户键,把 refresh token 留在服务端,并且只向浏览器发送应用自己的 session cookie。不要把返回的 token response 序列化进 cookie。
令牌验证提醒
Section titled “令牌验证提醒”OIDC ID token 通过 oidc.sudomimus.com/.well-known/jwks.json 上的 JWKS 验证 —— 这是 OIDC 标准机制,OIDC 库会自动做。
但 /token 返回的 access_token 不是使用平台级 OIDC JWKS 验证的令牌。它是结构与 Connect 相同的应用专属访问令牌:payload sub 是配对用户键,sid 是 ApplicationSession,jti 是这个 token 实例。需要按 kid 使用 https://session-api.sudomimus.com/applications/{applicationAnchor}/jwks.json 验证。如果 OIDC 库无法为 access token 配置独立 JWKS,也可以将它视为不透明字符串,只通过 /userinfo 获取声明。详见令牌与验证。