Files
wmailhub/docs/12_ZITADEL_SSO.md
T

3.8 KiB

12. ZITADEL 统一登录(v2.9.0)

Console 支持通过自建 ZITADEL 实例做 SSO 登录:OIDC Authorization Code + PKCE(公共客户端,无 client secret),与用户名密码登录双通道并存,互不替代。

12.1 配置

入口:设置 → ZITADEL 统一登录 tab。配置存 settings 表,部署环境变量兜底(与 Cloudflare OAuth 同语义):

来源 说明
settings 后台设置保存的 { issuer, client_id, enabled, jit }(主)
env OIDC_ISSUER + OIDC_CLIENT_ID(可选 OIDC_ENABLED=false 关闭)
none 未配置,登录页不显示统一登录入口
  • Issuer:如 https://auth.my.qiaonao.net(必须 HTTPS;本机联调允许 http://localhost)。
  • Client ID:在 ZITADEL 控制台(项目 → 应用)为 WmailHub 建应用,认证方式选 Code + PKCE,不需要 secret。
  • 回调地址(Redirect URI):{Console origin}/auth/callback,必须登记进 ZITADEL 应用。
  • Scope:固定 openid profile(userinfo 取身份;实例未配 SMTP 时拿不到 email claim,不影响登录)。

12.2 流程与安全

GET /auth/status    公开探测 { enabled, issuer }(登录页据此显示按钮)
GET /auth/login     生成 state + PKCE verifier 存 oidc_states(10 分钟,一次性)→ 302 授权端点
GET /auth/callback  state 用后即删 → code 换 token → userinfo 取 sub → 解析/JIT 建号 → 签发本地会话 cookie → 302 /
  • 身份主键 (issuer, subject),存 oidc_identities;email 只是资料展示,绝不作为身份键(实例无 SMTP,邮箱可能缺失)。
  • 令牌只在服务端瞬时使用(换 userinfo),不落库、不进前端、不进日志;身份取 userinfo 的 sub,不做 ID Token 签名验证(access_token 由本服务直接从 token 端点换得)。
  • 回跳路径只接受站内路径(防开放重定向);所有失败 302 回 /login?error=<code>,登录页映射中文文案。
  • 本地会话(HMAC cookie)独立于 IdP:IdP 挂掉时已登录用户不受影响;IdP 不可达时 discovery 报错走 oidc_unavailable,进程不 panic。

12.3 账号模型

  • 登录成功即管理员:本平台没有权限设置,任何能完成统一登录的身份与密码登录用户权限完全一致。
  • JIT(自动建号):设置里可开关(默认开)。开启时未绑定的身份首次登录自动创建本地用户(用户名取展示名兜底链 slug 化,冲突加 -2/-3;密码哈希为随机串,即该账号不能密码登录);关闭时未绑定身份拒绝登录(oidc_not_linked)。
  • 展示名兜底链:name → given_name + family_name → preferred_username → nickname → 邮箱本地部分 → 统一账号 <sub 前 12 位>。
  • 重复登录同一 subject:刷新 display_name/email 资料,不报冲突。
  • 登出仅清本地会话,不做 IdP end_session(避免把整个 ZITADEL SSO 会话登掉,影响其他接入应用)——有意偏离,非遗漏。

12.4 数据库(migrations/0006)

  • oidc_identities(id, issuer, subject, user_id, display_name, email, created_at, updated_at),UNIQUE(issuer, subject)
  • oidc_states(state, code_verifier, return_to, created_at, expires_at),一次性

12.5 验收自查(v2.9.0 实测)

  • Authorization Code + PKCE(S256),非 implicit(token 请求带 code_verifier 有测试)
  • state 一次性、10 分钟过期、校验后才换码(重放测试)
  • 令牌不落前端、不落库、不进日志
  • 身份主键 (issuer, subject),email 仅资料
  • 本地会话独立于 IdP;登录页/应用无 token 互踢死循环(前端无 token 概念,纯 cookie 会话)
  • 登出仅本地会话(有意偏离,见 12.3)
  • 密码登录通道完整保留(回归测试覆盖)
  • JIT 可配置;开启时建号即管理员(按需求)
  • 日志无敏感信息(operation_logs 只记动作/用户名/issuer/开关布尔)