v2.9.1: 统一登录单用户化——删除 JIT 开关与 oidc_identities 映射表(0007),任意 ZITADEL 身份登录唯一用户,上次登录身份存 settings 展示
This commit is contained in:
+12
-15
@@ -1,4 +1,4 @@
|
||||
# 12. ZITADEL 统一登录(v2.9.0)
|
||||
# ZITADEL 统一登录(v2.9.0;v2.9.1 单用户化)
|
||||
|
||||
Console 支持通过自建 ZITADEL 实例做 SSO 登录:OIDC **Authorization Code + PKCE**(公共客户端,无 client secret),与用户名密码登录双通道并存,互不替代。
|
||||
|
||||
@@ -8,7 +8,7 @@ Console 支持通过自建 ZITADEL 实例做 SSO 登录:OIDC **Authorization Cod
|
||||
|
||||
| 来源 | 说明 |
|
||||
|------|------|
|
||||
| `settings` | 后台设置保存的 `{ issuer, client_id, enabled, jit }`(主) |
|
||||
| `settings` | 后台设置保存的 `{ issuer, client_id, enabled }`(主) |
|
||||
| `env` | `OIDC_ISSUER` + `OIDC_CLIENT_ID`(可选 `OIDC_ENABLED=false` 关闭) |
|
||||
| `none` | 未配置,登录页不显示统一登录入口 |
|
||||
|
||||
@@ -22,35 +22,32 @@ Console 支持通过自建 ZITADEL 实例做 SSO 登录:OIDC **Authorization Cod
|
||||
```
|
||||
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 /
|
||||
GET /auth/callback state 用后即删 → code 换 token → userinfo 取 sub → 登录唯一用户 → 签发本地会话 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 账号模型
|
||||
## 12.3 账号模型(v2.9.1 单用户化)
|
||||
|
||||
- **登录成功即管理员**:本平台没有权限设置,任何能完成统一登录的身份与密码登录用户权限完全一致。
|
||||
- **JIT(自动建号)**:设置里可开关(默认开)。开启时未绑定的身份首次登录自动创建本地用户(用户名取展示名兜底链 slug 化,冲突加 `-2/-3`;密码哈希为随机串,即该账号不能密码登录);关闭时未绑定身份拒绝登录(`oidc_not_linked`)。
|
||||
- **单用户系统**:没有身份映射表、没有 JIT 建号、没有权限设置。任何能完成统一登录的身份,直接登录本平台唯一用户(种子 admin,用户名/密码均可在设置里改)。
|
||||
- 上次统一登录身份(issuer/subject/展示名/时间)记录在 settings(`oidc_last_login`)供设置页展示,不参与任何判定。
|
||||
- 展示名兜底链:`name` → `given_name + family_name` → `preferred_username` → `nickname` → 邮箱本地部分 → `统一账号 <sub 前 12 位>`。
|
||||
- 重复登录同一 subject:刷新 `display_name/email` 资料,不报冲突。
|
||||
- **登出仅清本地会话**,不做 IdP end_session(避免把整个 ZITADEL SSO 会话登掉,影响其他接入应用)——有意偏离,非遗漏。
|
||||
|
||||
## 12.4 数据库(migrations/0006)
|
||||
## 12.4 数据库
|
||||
|
||||
- `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)`,一次性
|
||||
- `oidc_states(state, code_verifier, return_to, created_at, expires_at)`,一次性(0006)
|
||||
- ~~`oidc_identities`~~:v2.9.0 引入的身份映射表,**0007 迁移已删除**(单用户系统不需要映射)
|
||||
|
||||
## 12.5 验收自查(v2.9.0 实测)
|
||||
## 12.5 验收自查(v2.9.1 实测)
|
||||
|
||||
- [x] Authorization Code + PKCE(S256),非 implicit(token 请求带 `code_verifier` 有测试)
|
||||
- [x] state 一次性、10 分钟过期、校验后才换码(重放测试)
|
||||
- [x] 令牌不落前端、不落库、不进日志
|
||||
- [x] 身份主键 (issuer, subject),email 仅资料
|
||||
- [x] 单用户:任意 ZITADEL 身份登录唯一用户,不建新账号(有测试);email 仅展示
|
||||
- [x] 本地会话独立于 IdP;登录页/应用无 token 互踢死循环(前端无 token 概念,纯 cookie 会话)
|
||||
- [x] 登出仅本地会话(有意偏离,见 12.3)
|
||||
- [x] 密码登录通道完整保留(回归测试覆盖)
|
||||
- [x] JIT 可配置;开启时建号即管理员(按需求)
|
||||
- [x] 日志无敏感信息(operation_logs 只记动作/用户名/issuer/开关布尔)
|
||||
- [x] 日志无敏感信息(operation_logs 只记动作/用户名/issuer/展示名)
|
||||
|
||||
+5
-5
@@ -60,14 +60,14 @@
|
||||
|
||||
## ZITADEL 统一登录(/auth)
|
||||
|
||||
OIDC Authorization Code + PKCE(无 client secret),与密码登录双通道并存;**登录成功即管理员**(本平台没有权限设置)。配置入口:设置 → ZITADEL 统一登录(issuer + client_id;回调地址 `{origin}/auth/callback` 需登记到 ZITADEL 应用)。详细设计见 docs/12。
|
||||
OIDC Authorization Code + PKCE(无 client secret),与密码登录双通道并存;**单用户系统——统一登录成功即登录本平台唯一账号**(没有权限设置)。配置入口:设置 → ZITADEL 统一登录(issuer + client_id;回调地址 `{origin}/auth/callback` 需登记到 ZITADEL 应用)。详细设计见 docs/12。
|
||||
|
||||
| 端点 | 说明 |
|
||||
|------|------|
|
||||
| `GET /auth/login` | 发起授权,302 到 IdP;可选 `?return=/<站内路径>` 指定回跳(默认 `/`)。未启用 302 → `/login?error=oidc_disabled` |
|
||||
| `GET /auth/callback` | IdP 回调(浏览器重定向,无需调用):一次性 state 校验 → 换码 → userinfo → 建号/匹配 → 下发会话 Cookie → 302 回站内;失败 302 → `/login?error=<code>` |
|
||||
| `GET /auth/callback` | IdP 回调(浏览器重定向,无需调用):一次性 state 校验 → 换码 → userinfo → 登录唯一用户 → 下发会话 Cookie → 302 回站内;失败 302 → `/login?error=<code>` |
|
||||
|
||||
错误码:`oidc_disabled` / `oidc_unavailable` / `state_invalid` / `oidc_exchange_failed` / `oidc_not_linked`(JIT 关闭且身份未绑定)。
|
||||
错误码:`oidc_disabled` / `oidc_unavailable` / `state_invalid` / `oidc_exchange_failed`。
|
||||
|
||||
## Console Web API(/api/*,Cookie 会话)
|
||||
|
||||
@@ -129,8 +129,8 @@ Webhook 生效顺序:**邮箱 → 域名 → 全局**,取第一个已配置的
|
||||
|
||||
| 方法与路径 | 说明 |
|
||||
|------------|------|
|
||||
| `GET /api/settings` | 全量读取:当前用户名、全局 Webhook(key 只回 last4)、API Key 元信息、Cloudflare OAuth、ZITADEL 统一登录(含回调地址与当前账号绑定身份) |
|
||||
| `PUT /api/settings` | 保存 `{ webhook?, cf_oauth?, oidc? }`;`webhook.key`/`cf_oauth.secret`:undefined 保留、空串清除、非空设置;`oidc: { issuer, client_id, enabled?, jit? }`(开关缺省沿用已存值) |
|
||||
| `GET /api/settings` | 全量读取:当前用户名、全局 Webhook(key 只回 last4)、API Key 元信息、Cloudflare OAuth、ZITADEL 统一登录(含回调地址与上次统一登录身份) |
|
||||
| `PUT /api/settings` | 保存 `{ webhook?, cf_oauth?, oidc? }`;`webhook.key`/`cf_oauth.secret`:undefined 保留、空串清除、非空设置;`oidc: { issuer, client_id, enabled? }`(enabled 缺省沿用已存值) |
|
||||
| `PUT /api/settings/password` | 改密码 `{ old_password, new_password }`(≥6 位) |
|
||||
| `PUT /api/settings/username` | 改用户名 `{ new_username, password }`(需当前密码;2-32 位字母/数字/`_ . -`;成功重签会话 Cookie;重名 409) |
|
||||
| `POST /api/settings/api-key` | 生成(轮换)API/MCP Key → `201 { key, created_at }`(明文仅此一次) |
|
||||
|
||||
Reference in New Issue
Block a user