12 KiB
Identity / Member / Permission 跨模組架構
Gateway 三個業務模組(auth / member / permission)與外部 ZITADEL(身份)、LDAP(企業目錄)、SCIM 2.0(企業 provisioning)的整合總覽。內容只談跨模組層次的設計決策與關聯;模組內部請看:
| 模組 | 文件 |
|---|---|
| auth | internal/model/auth/README.md |
| member | internal/model/member/README.md |
| permission | internal/model/permission/README.md |
| notification | internal/model/notification/README.md |
分層公約:docs/model.md。
1. 設計目標
| 目標 | 說明 |
|---|---|
| 統一身份 | ZITADEL 為 IdP(OIDC、LDAP IdP、Social Login) |
| 業務會員 | Gateway member 管 tenant-scoped profile |
| 細粒度授權 | Gateway permission(Casbin RBAC + Permission Tree);B2B 租戶可自定義 Role 並勾選 Permission |
| 對外 Token | Gateway 自簽 CloudEP JWT;ZITADEL OIDC token 只在 /auth/token/exchange 用一次 |
| 規模 | 全平台 100 萬+ 會員;單租戶可達 50 萬 |
| UID | 人類可讀帶租戶前綴(如 ACME-10000003);唯一鍵 (tenant_id, uid) |
2. 核心原則
-
職責分離
auth:你是誰(Authentication)member:你的業務資料(Profile)permission:你能做什麼(Authorization)
-
LDAP 不做登入 bind 登入驗證一律走 ZITADEL LDAP IdP;Gateway LDAP client 只做 Directory Sync(read-only)。
-
租戶隔離 所有持久化資料以
tenant_id為邊界;JWTtenant_id與請求資源不一致 → 403。 -
B2B 權限自定義
- 平台 seed 全局 Permission Tree(含
http_path+http_method) - 租戶建自訂 Role,從 Tree 勾選 Permission(自動補 parent)
- Casbin 以
(tenant_id, role_key, path, method)比對,跨租戶同名角色互不污染
- 平台 seed 全局 Permission Tree(含
-
身份驗證 vs 業務驗證分層
級別 由誰負責 範例 身份級 ZITADEL Org Policy 登入 MFA、註冊 email、忘記密碼、帳號鎖定 業務級 Gateway member業務 email/phone OTP、TOTP step-up Gateway 不讀 ZITADEL.email_verified當業務守門;改讀member.business_email_verified。 -
UseCase 原子性 UseCase 為 atomic primitive,禁止互相呼叫;跨原子流程(OTP → 寄信 → 驗碼 → flip flag)一律在
internal/logic/<module>/編排。
3. 模組依賴方向
handler → logic → model/{module}/domain/usecase(介面)
↓
model/{module}/repository(介面)
↓
MongoDB / Redis
logic 可同時呼叫多個 model 的 usecase(編排)
usecase 之間互不依賴
auth/logic → member.usecase(CreateUnverified / Activate / EnsureFromOIDC)
auth/logic → permission.usecase(SyncRolesFromClaims,未來)
auth/logic → notification.Notifier(OTP / 註冊信)
member/logic → notification.Notifier(業務驗證、step-up OTP)
permission → 獨立,不反向依賴
4. 外部系統分工
| 能力 | ZITADEL | auth | member | permission | notification |
|---|---|---|---|---|---|
| OIDC / Social Login | ? | callback 換 JWT | EnsureFromOIDC | SyncRoles | — |
| 平台 Email + Password 註冊 | 建 user(local) | logic 編排 | LifecycleUseCase | — | 寄 OTP |
| 身份 MFA 強制 | ? Org Policy | — | — | — | — |
| LDAP 登入 | ? LDAP IdP | — | — | Group→Role | — |
| 對外 JWT | — | ? CloudEP | — | — | — |
| 業務 TOTP (Authenticator) | — | — | ? AES-GCM 存 secret | — | — |
| 業務 Email/Phone OTP | — | — | ? OTPUseCase | — | ? 模板 + 發送 |
| API 細粒度權限 | 粗 Role | — | — | ? Casbin | — |
| SCIM Users/Groups | 可同步 | — | 業務寫入 | Group→Role | — |
| Directory Sync | — | — | Worker | Group→Role | 同步異常告警 |
4.1 租戶 ? ZITADEL Org
1 CloudEP Tenant = 1 ZITADEL Organization = 1 資料隔離邊界
| 欄位 | 來源 | 用途 |
|---|---|---|
tenant_id |
Gateway 內部 | 分片鍵 / 授權邊界 |
tenant.org_id |
ZITADEL org_id |
身份系統對應 |
member.zitadel_user_id |
ZITADEL sub |
身份映射 |
member.uid |
UIDGenerator |
業務可讀主鍵 |
4.2 Tenant 建立(Saga)
1. POST /api/v1/admin/tenants
→ Mongo upsert tenants { status: provisioning, org_id: "" }
2. ZITADEL Mgmt.CreateOrganization(slug)
→ org_id
3. UPDATE tenants { org_id, status: active }
4. seed system roles + Casbin reload
失敗補償(cron 每 5 分鐘):
- 步驟 2 失敗 → status=failed,指數退避,3 次後告警
- 步驟 3 失敗 → status=orphan_zitadel_org,掃描補綁
4.3 租戶類型
| 類型 | 登入 | LDAP | 權限 |
|---|---|---|---|
| B2C | Email / Social | — | 系統預設 Role(唯讀模板) |
| B2B | ZITADEL → LDAP IdP | ? | 完全自定義 Role + Permission |
| Hybrid | Social + LDAP | ? | B2B 自定義;外部客戶用 B2C 模板 |
5. UID 設計
| 規則 | 值 |
|---|---|
| 格式 | {TenantUIDPrefix}-{Sequence} |
| Prefix | 2~4 個大寫字母(Tenant 建立時設,全域唯一) |
| 起始序號 | 10,000,000(7 位起跳,避免 ACME-1 這種短 UID) |
| 唯一鍵 | (tenant_id, uid) |
| 範例 | ACME-10000003 |
實作:member.UIDGenerator → Redis INCR member:seq:{tenant};首次 INCR=1 時自動 INCRBY 9_999_999 補到起跳值。
6. Permission 模型
平台層 (Platform-wide) 租戶層 (per-tenant)
┌────────────────┐ ┌─────────┐
│ Permission │ ── 勾選 ──→ │ Role │
│ Catalog (Tree) │ │ │
└────────────────┘ │ ↓ 指派 │
│ UserRole│
│ │
│ ↑ 對映 │
│RoleMap │
└─────────┘
↓ LoadPolicy
Casbin Enforcer (Redis adapter)
↓
CasbinRBAC Middleware
| 概念 | 說明 |
|---|---|
| Permission | 平台級節點(樹狀 dot notation)。Name 不可改;廢棄走 status=close |
| Role | 租戶內角色,(tenant_id, key) unique;Key 不可改(外部 IdP mapping 直接綁 Key) |
| RolePermission | 全量取代式更新;自動補 parent permission |
| UserRole | source 區分 manual / zitadel / ldap / scim;ReplaceForSource 不洗 manual |
| RoleMapping | 外部 group/role → 內部 role.key,給 SyncFromX 翻譯 IdP claims |
| Casbin Rule | [tenant, role.key, http_path, http_methods, perm.name] |
多 pod 同步:Redis Pub/Sub 即時 + 5 分鐘 cron 兜底(兜底建議在 svc 層排程,本模組不內建)。
7. Middleware 鏈
HTTP Request
↓
[AuthJWT] ← 解析 Bearer → actor.WithActor(ctx, tenant, uid)
↓
[CasbinRBAC] ← 必須在 AuthJWT 之後;any-allow 過所有 open role
↓
handler → logic → usecase
掛載方式:在 .api 的 @server (middleware: AuthJWT,CasbinRBAC) 宣告,make gen-api 會把 middleware 寫進 routes.go。禁止 在 gateway.go 用 server.Use(...) 全域掛。詳見 generate/api/README.md。
Actor 一律用 internal/library/actor.WithActor / ActorFromContext,不要在各 logic package 自定 actorKey struct{}。
8. Notification 與業務驗證
詳細:
internal/model/notification/README.md、internal/model/member/README.md
| 流程 | 編排層 | 用到的 atomic |
|---|---|---|
| 平台註冊 email OTP | logic/auth.RegisterLogic |
member.OTP.Generate + notifier.Send(verify_registration_email) |
| 業務 email/phone 驗證 | logic/member.startVerification + confirmVerification |
VerifyRate → OTP.Generate → Notifier.Send → Profile.SetXxxVerified |
| TOTP 綁定 / step-up | logic/member.*_t_o_t_p_* |
TOTP.StartEnroll / ConfirmEnroll / VerifyCode |
Notification 是 library-style domain,由 ServiceContext 啟動
RetryWorker(異步重試 + DLQ)。Provider chain:SMTP / SES / Mitake / Mock,依Sortfailover。
9. Rate Limit / Audit(規劃)
| 主題 | 現況 | 規劃 |
|---|---|---|
| OTP / 業務驗證 cooldown | ? 已實作(member.VerifyRate,Redis SETNX + daily INCR) |
— |
| HTTP 全域 rate limit | 未實作 | 建議 go-zero middleware + Redis sliding-window(ZSET),key = rl:{dim}:{path} |
| Audit Log | 未實作 | audit_logs collection(獨立 DB 建議);critical 同步寫,info 批次;TTL 90d |
10. 規模考量(100 萬+ 會員)
| 項目 | 建議 |
|---|---|
members 分片 |
hashed tenant_id(單租戶 50 萬可走複合 unique index 不分片) |
| Casbin policy | 一 tenant 一 enforcer,lazy 建立;Redis Set 儲存 rule |
| JWT 黑名單 | 只黑名單已撤銷的 jti,TTL = 至自然過期 |
| Member list 查詢 | 強制帶 tenant_id + (create_at, _id) cursor 分頁 |
| Notification DLQ | 超過 MaxRetry 寫入 notification_dlq;admin CLI 重試 |
11. 已決策事項
| 主題 | 決策 |
|---|---|
| Tenant 建立順序 | Gateway 先建草稿(status=provisioning)→ ZITADEL CreateOrg → 補 org_id;補償 cron |
| Permission name 改名 | 禁止(被 RolePermission / Casbin policy.name 引用);廢棄改 status=close |
| Role.Key 改名 | 禁止(外部 IdP mapping 直接綁) |
is_system Role |
不可刪、不可改 status |
manual source ReplaceForSource |
拒絕(防 SyncFromX 誤洗手動指派) |
| Gateway 統一註冊 | 取代 ZITADEL Hosted Page;email + social 同 UX,invite 由 logic 驗 |
| Email / SMS OTP | 由 Gateway notification 自送,不轉 ZITADEL Notification |
| 業務 TOTP | 與 ZITADEL TOTP 獨立;secret AES-GCM 存 Mongo |
| Email 驗證守門 | 讀 member.business_email_verified,不讀 ZITADEL email_verified |
| UseCase 不互呼 | 強制:跨原子流程在 logic 編排(model.md §6.1) |
| Casbin 多 pod 同步 | Redis Pub/Sub 即時 + 5min cron 兜底 |
| Permission Catalog 變更 | seed CLI(cmd/permission-seed);UI 不可直接改 |
12. 目錄結構
internal/
├── library/
│ ├── actor/ # ctx actor helper(跨 module 共用 key)
│ ├── crypto/ # AES-GCM cipher(TOTP secret KEK)
│ ├── errors/ # 8 碼 SSCCCDDD
│ ├── mongo/ # DocumentDB + cache
│ ├── redis/ # go-zero client + Pub/Sub
│ ├── validate/ # struct 驗證
│ └── zitadel/ # ZITADEL HTTP client
├── middleware/
│ ├── authjwt_middleware.go # 解 JWT → actor
│ └── casbinrbac_middleware.go # any-allow RBAC
├── model/
│ ├── auth/ # 邀請碼 / 註冊 metadata / OAuth session / JWT
│ ├── member/ # Tenant / Member / Identity / OTP / TOTP / UID
│ ├── notification/ # Email / SMS / 模板 / Worker / DLQ
│ └── permission/ # Permission Catalog / Role / RolePermission / Casbin
└── worker/
└── notification_retry/ # Notification RetryWorker runner
13. 設定檔
主要區塊(完整見 etc/README.md + etc/gateway.dev.example.yaml):
Mongo: {...}
Redis: {...}
Auth: # JWT secret / TTL / RegistrationSessionTTLSeconds
Member: # OTP / TOTP / Registration
Permission: # Casbin.Enabled / ModelPath / PolicyAdapter / Cache / Reload
Notification:# Email / SMS / Async / RatePerTenant
Zitadel: # Issuer / ServiceUserToken / Google* / DefaultOrgID
14. 進度速覽
| 模組 | 狀態 | 備註 |
|---|---|---|
| auth | ? 已實作 | 統一註冊 (Email + Google) / login / token refresh / logout |
| member | ? 已實作 | profile / 業務 email/phone OTP / TOTP 全流程 |
| permission | ? 已實作 | Catalog / Role CRUD / RolePermission / UserRole / RoleMapping / Casbin RBAC |
| notification | ? 核心完成 | Email/SMS sync+async + DLQ;HTTP admin API 未做 |
| LDAP Directory Sync | ? 未實作 | 依 SCIM / SyncFromX 推進 |
| SCIM 2.0 server | ? 未實作 | 依企業客戶需求 |
| Audit Log | ? 未實作 | 規劃 collection + TTL 90d |
| HTTP Rate Limit middleware | ? 未實作 | OTP cooldown 已有;全域待補 |