Files
wmailhub/docs/12_ZITADEL_SSO.md
T

54 lines
3.5 KiB
Markdown

# ZITADEL 统一登录(v2.9.0;v2.9.1 单用户化)
Console 支持通过自建 ZITADEL 实例做 SSO 登录:OIDC **Authorization Code + PKCE**(公共客户端,无 client secret),与用户名密码登录双通道并存,互不替代。
## 12.1 配置
入口:设置 → **ZITADEL 统一登录** tab。配置存 settings 表,部署环境变量兜底(与 Cloudflare OAuth 同语义):
| 来源 | 说明 |
|------|------|
| `settings` | 后台设置保存的 `{ issuer, client_id, enabled }`(主) |
| `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 → 登录唯一用户 → 签发本地会话 cookie → 302 /
```
- 令牌只在服务端瞬时使用(换 userinfo),不落库、不进前端、不进日志;身份取 userinfo 的 `sub`,不做 ID Token 签名验证(access_token 由本服务直接从 token 端点换得)。
- 回跳路径只接受站内路径(防开放重定向);所有失败 302 回 `/login?error=<code>`,登录页映射中文文案。
- 本地会话(HMAC cookie)独立于 IdP:IdP 挂掉时已登录用户不受影响;IdP 不可达时 discovery 报错走 `oidc_unavailable`,进程不 panic。
## 12.3 账号模型(v2.9.1 单用户化)
- **单用户系统**:没有身份映射表、没有 JIT 建号、没有权限设置。任何能完成统一登录的身份,直接登录本平台唯一用户(种子 admin,用户名/密码均可在设置里改)。
- 上次统一登录身份(issuer/subject/展示名/时间)记录在 settings(`oidc_last_login`)供设置页展示,不参与任何判定。
- 展示名兜底链:`name` → `given_name + family_name` → `preferred_username` → `nickname` → 邮箱本地部分 → `统一账号 <sub 前 12 位>`。
- **登出仅清本地会话**,不做 IdP end_session(避免把整个 ZITADEL SSO 会话登掉,影响其他接入应用)——有意偏离,非遗漏。
## 12.4 数据库
- `oidc_states(state, code_verifier, return_to, created_at, expires_at)`,一次性(0006)
- ~~`oidc_identities`~~:v2.9.0 引入的身份映射表,**0007 迁移已删除**(单用户系统不需要映射)
## 12.5 验收自查(v2.9.1 实测)
- [x] Authorization Code + PKCE(S256),非 implicit(token 请求带 `code_verifier` 有测试)
- [x] state 一次性、10 分钟过期、校验后才换码(重放测试)
- [x] 令牌不落前端、不落库、不进日志
- [x] 单用户:任意 ZITADEL 身份登录唯一用户,不建新账号(有测试);email 仅展示
- [x] 本地会话独立于 IdP;登录页/应用无 token 互踢死循环(前端无 token 概念,纯 cookie 会话)
- [x] 登出仅本地会话(有意偏离,见 12.3)
- [x] 密码登录通道完整保留(回归测试覆盖)
- [x] 日志无敏感信息(operation_logs 只记动作/用户名/issuer/展示名)