OIDC 接入
本文面向在本企业内接入统一身份的应用开发者,说明应用注册与审批、标准 OIDC 参数、 令牌语义、退出联动与常见错误。
yufu 是单企业独立部署的统一身份与 SSO 提供方。一个进程同时提供三个入口:
- 账户中心:
/account/*,本人资料、授权应用、同意、会话与 RP 会话 - 开发者控制台:
/developer/*,本人应用的注册、配置、版本、提交审批与成员 - 企业管理控制台:
/admin/*,账户、应用、组、分配、审批、审计与历史纳管
接入采用企业邀约 / 审批制,不提供公开应用市场。当前明确不做:多租户、外部 IdP 联邦、 公开开发者自助注册、强认证(WebAuthn 等)。接入方不得依赖这些能力。
- 管理员为开发者授予开发者资格;未获资格时
/developer/*返回拒绝。 - 开发者在
/developer/apps注册应用,分别配置development与production环境。 - 生产配置提交后进入审批;管理员在
/admin/apps/:id审阅固定申请快照并批准。 - 管理员在
/admin/apps/:id/assignments为账户或组分配应用;测试环境可额外授予测试用户访问。 - 用户首次登录时在同意页确认请求的 scope;未获分配、未批准或未同意的请求不会签发 code。
开发环境用于联调,生产环境用于真实用户。两环境有独立的 client、回调白名单、access audience、 secret 与配置版本;不要把开发凭据带到生产。
Discovery 与端点
Section titled “Discovery 与端点”Issuer 为部署的公开基址(生产必须 HTTPS)。Discovery 位于:
GET {issuer}/.well-known/openid-configurationGET {issuer}/.well-known/jwks.json| 端点 | 路径 | 说明 |
|---|---|---|
| Authorization | /oidc/authorize |
仅 response_type=code、response_mode=query、PKCE S256 |
| Token | /oidc/token |
authorization_code 与 refresh_token |
| UserInfo | /oidc/userinfo |
Bearer access token |
| Introspection | /oidc/introspect |
Reader 凭据(client_secret_basic / post) |
| Revocation | /oidc/revoke |
支持 access / refresh token |
| End session | /oidc/end-session |
仅在 RS256 签名时发布 |
| JWKS | /.well-known/jwks.json |
RS256 公钥 |
支持的 scope 为 openid、profile、email,其中 openid 必填。签名算法以部署配置为准;
参考示例按 RS256 验证。
机密 Web 客户端
Section titled “机密 Web 客户端”- 服务端持有
client_secret,使用client_secret_basic或client_secret_post兑换 code。 - 回调地址必须与注册值逐字符一致,包括 scheme、host、port、path,不允许通配或前缀匹配。
- 浏览器只接触 issuer、client id、redirect URI、scope;secret 与 Reader 凭据不得进入前端。
公共 SPA 客户端
Section titled “公共 SPA 客户端”- 不配置
client_secret,使用授权码 + PKCES256。 - code 兑换由 SPA 直接完成或由同站 BFF 完成;本仓库示例选择 SPA 直接兑换。
- 回调页必须是 SPA 的注册回调,不要复用 Web 客户端的回调。
CORS 与 Origin
Section titled “CORS 与 Origin”- 保护 API 只接受注册的 SPA origin。带
Origin的请求必须精确匹配 scheme/host/port, 不匹配直接 403,且在读取 token 之前拒绝。 - 预检只允许
authorization、content-type请求头与POST, OPTIONS方法。 - 中心的管理与账户接口使用同源 Cookie + CSRF,不使用旧 Bearer;跨站调用不应依赖中心 Cookie。
身份映射与令牌语义
Section titled “身份映射与令牌语义”sub是稳定主体标识;同一(iss, sub)在 A、B 两个应用间保持一致。应用应把iss + sub作为本地用户键,不要用 email 或 username 作为主键。profilescope 披露name、preferred_username;emailscope 披露email、email_verified。 未授予的 scope 不披露对应字段。- ID Token 的
aud是客户端 id,包含nonce、auth_time、sid;sid是协议会话标识, 用于退出联动。 - Access Token 的
typ为at+jwt,aud是应用环境的 access audience(例如urn:app:production),不是 client id。保护 API 必须同时校验签名、issuer、audience、typ与有效期。 - 内省响应包含
active、scope、client_id、username、token_type、exp、sub; 不保证返回协议sid。按会话清理缓存时,同时按sid与sub清理。 - Access Token 中的
session_id是凭据会话 id,不是协议sid,不要混用。
保护 API 的在线检查
Section titled “保护 API 的在线检查”参考实现共用同一套语义:
- 正向结果默认缓存 2 秒;超过 2 秒必须重新内省。
- 任何结果(含层间传递)绝对年龄不得超过 5 秒;超过即失败关闭。
- 年龄从内省请求开始的单调时刻计算,慢内省不会获得新的 2 秒窗口。
- 层间传递保留原始
verifiedAt,接收层不得重置 TTL。 - token 已过期、内省不可用、
active=false、client 不匹配、subject 不匹配都拒绝; 拒绝时不执行受保护副作用。
5 秒是硬上限,不是“每 5 秒刷新一次”的授权周期。需要更强实时性的写操作应缩短缓存或直接内省。
- 本地退出:只清本应用会话,不影响其他应用。
- 当前根退出:清当前中心浏览器会话及其派生的 RP 会话。
- 所有根退出:清该用户全部中心根与 RP 会话。
- RP 发起退出:使用
id_token_hint与post_logout_redirect_uri,端点由 discovery 发布。 - 后通道退出:
typ=logout+jwt、RS256、aud=client_id、events仅含后通道事件、 必须有jti与sid或sub、不得含nonce、exp-iat ≤ 120秒。接收方必须校验 签名 / issuer / audience / 事件 / 时效,并按jti幂等。
- 把 client id 当作 access token 的
aud:会拒绝所有保护请求,应使用环境注册的 access audience。 - 在 SPA 中放
client_secret或 Reader 凭据:会进入浏览器包,属于凭据泄漏。 - 回调地址使用通配或尾斜杠不一致:授权请求会被拒绝。
- 只按
sid清理授权缓存:内省可能没有sid,必须同时按sub。 - 把 ID Token 当 access token 调用 API:
typ不是at+jwt,必须拒绝。 - 用旧 Bearer 访问中心管理接口:中心控制台使用同源 Cookie + CSRF。