跳转到内容

OIDC 流程

查看 Markdown

如果应用已经支持 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 协议 通常更短。

Sudomimus 发布标准 OIDC discovery 文档。将库的 issuer 设置为 https://oidc.sudomimus.com 后,客户端库可以自动读取其余配置:

Terminal window
curl https://oidc.sudomimus.com/.well-known/openid-configuration

discovery 文档声明:

  • response_types_supported["code"](仅 authorization code flow)。
  • grant_types_supported["authorization_code", "refresh_token"]
  • scopes_supported["openid", "email", "profile", "offline_access"]
  • 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"]

with.sudomimus.com 中,为需要启用 OIDC 的应用完成以下配置:

  1. 添加一条 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"
    }
    }
  2. 与其他应用一样配置 Layer 1 和 Layer 2:至少添加一条认证规则(例如 PASSKEY_USERNAMELESSPASSKEY_REASONED)和一条身份准入规则(例如允许特定邮箱的 EMAIL 规则)。OIDC 流程仍使用平台的同一套用户认证挑战。

  3. 选择客户端认证方式

    • private_key_jwt(推荐用于机密客户端)—— RP 持有私钥,在 /token 上用 JWT assertion 做客户端认证。签名密钥就是应用的 client-auth 私钥(和 Connect 协议中 /establish 用的同一把)。
    • client_secret_basic(机密客户端)—— RP 在 /token 的 HTTP Authorization: Basic 头中携带共享密钥。
    • client_secret_post(机密客户端)—— RP 在 /token 的表单体中发送共享密钥(client_id + client_secret 参数)。
    • none —— 仅限公开客户端(SPA、无后端的移动应用)。必须使用 PKCE。

应用的 applicationAnchor 就是你的 client_id

把用户浏览器跳转到 /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
&state=<csrf-token>
&nonce=<random-nonce>
&code_challenge=<S256-of-verifier>
&code_challenge_method=S256

必填:client_idredirect_uriresponse_type=codescope(必须包含 openid)、code_challengecode_challenge_method=S256。 选填(建议带上):statenonce

Sudomimus 会将用户跳转到 via.sudomimus.com,完成 Layer 1 允许的认证方式。认证和身份准入检查成功后,浏览器会携带 code 和原始 state 返回 redirect_uri

/token 提交授权码。按照 OIDC 规范,请求体必须使用 application/x-www-form-urlencoded

Terminal window
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_idsub = client_idaud = 完整且精确的 token endpoint URL(生产环境为 https://oidc.sudomimus.com/token)、全新的 jtiiatexp(与 iat 相差不超过 300 秒)。签名算法为 RS256。

成功响应(JSON):

{
"access_token": "<JWT>",
"token_type": "Bearer",
"expires_in": 10800,
"id_token": "<JWT>",
"scope": "openid email profile",
"refresh_token": "<JWT —— 只在请求了 'offline_access' 时返回>"
}
  • id_token —— 由 Sudomimus 的平台级 OIDC 密钥签发,通过 https://oidc.sudomimus.com/.well-known/jwks.json 验证。它包含标准 OIDC 声明(isssubaudexpiat,以及可选的 nonceemailname)。
  • access_token —— 由应用的 token-signing 密钥签发,结构与 Connect 协议的访问令牌相同。
  • refresh_token —— 仅在请求 offline_access scope 时返回。
Terminal window
curl https://oidc.sudomimus.com/userinfo \
-H "Authorization: Bearer $ACCESS_TOKEN"

返回 scope 允许的声明:

{
"sub": "<sector subject>",
"email": "<只在授予 'email' scope 时返回>",
"name": "<只在授予 'profile' scope 时返回>"
}

sub扇区主体(sector subject) —— 按扇区维度、应用可见的标识符(与 id_tokensub 相同),而非原始账户 UUID。/userinfo 同时支持 GETPOST

如果请求了 offline_access 并拿到了 refresh token,去 /token 兑换:

Terminal window
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

可以传入可选的 scope,请求原始授权范围的子集。根据 OIDC §12.1,刷新得到的 ID token 不会包含新的 nonce

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 完全匹配)。它不会自动吊销 refresh token —— 真正使底层会话失效要调 Connect 的 /logout(单个会话)或 /revoke-all(账户的所有会话)。详见 管理会话

绝大多数现代 OIDC 库(Node 的 openid-client、Python 的 authlib、Java 的 nimbus-jose-jwt 等)都能从 issuer URL 自动发现,并自动处理 PKCE、JWKS、令牌验证。一个最小的 Node 示例:

import { Issuer } from "openid-client";
const issuer = await Issuer.discover("https://oidc.sudomimus.com");
const client = new issuer.Client({
client_id: "my-app",
redirect_uris: ["https://app.example.com/oidc/callback"],
response_types: ["code"],
token_endpoint_auth_method: "none", // 或 "private_key_jwt"
});
// 登录时:
const codeVerifier = generators.codeVerifier();
const codeChallenge = generators.codeChallenge(codeVerifier);
const authUrl = client.authorizationUrl({
scope: "openid email profile",
code_challenge: codeChallenge,
code_challenge_method: "S256",
state, nonce,
});
// 回调时:
const params = client.callbackParams(req);
const tokenSet = await client.callback(redirectUri, params, { code_verifier: codeVerifier, state, nonce });
const userinfo = await client.userinfo(tokenSet.access_token);

OIDC ID token 通过 oidc.sudomimus.com/.well-known/jwks.json 上的 JWKS 验证 —— 这是 OIDC 标准机制,OIDC 库会自动做。

/token 返回的 access_token 不是使用平台 JWKS 验证的令牌。它是应用专属的访问令牌,结构与 Connect 流程相同,需要使用 POST /info 返回的 applicationPublicKey 验证。如果客户端库会在本地验证访问令牌,请为它配置 /info 提供的密钥;也可以将访问令牌视为不透明字符串,只通过 /userinfo 获取声明。详见令牌与验证