docs: Casdoor SSO 部署运维手册 + 泛化 E2E 检查脚本(沉淀 31.23 踩坑)
CodeQL Advanced / Analyze (actions) (push) Canceled after 0s
CodeQL Advanced / Analyze (java-kotlin) (push) Canceled after 0s
CodeQL Advanced / Analyze (javascript-typescript) (push) Canceled after 0s
CodeQL Advanced / Analyze (python) (push) Canceled after 0s

This commit is contained in:
figmar
2026-09-01 19:31:08 +08:00
parent f8c605b858
commit 20a1c8b569
2 changed files with 183 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
# DeepData × Casdoor SSO 部署运维手册(31.23)
> 适用范围:DeepData(`com.deepdata`)网关单点登录接入自建 Casdoor v2.153.0。
> 生产实例:31.23(Casdoor `:1013`,DeepData 网关 `:8080`,前端/入口 `deepdata.yhdd.apescale.com:1014`)。
> 代码版本:≥ `4e2ec55`(M3:移除 accountOnly,本地登录与 SSO 共存;登录时自动建号,无定时同步)。
## 1. 架构与登录流
```
浏览器 → DeepData 网关 /api/user/oauth/login?next=…
→ 302 Casdoor /login/oauth/authorize(state+next 存 HttpOnly cookie,TTL 300s)
→ Casdoor 登录/同意
→ 回调 /api/user/oauth/callback?code&state
→ 网关换 token → JWKS 验 id_token → upsert 本地用户(自动建号/刷组刷角色)
→ 302 next?sso_token=<本地HS256 JWT>(含 role/groups claims)
→ 前端 callback.html 种 token 进入应用
```
失败时回跳 `next?sso_error=<code>`(`state_mismatch` / `code_exchange` / `id_token_verify` / `internal`)。
角色映射:Casdoor 组 `deepdata-admin|dev|annotator|viewer`(组路径 `datamate/<group>`)→ 本地 `role`,由 `RoleResolver` 按「映射 > 主角色 > defaultRole」解析;组与角色随每次 SSO 登录刷新。
## 2. Casdoor 侧一次性配置(生产现状)
| 对象 | 值 | 说明 |
|------|-----|------|
| 组织 | `admin/built-in`、`admin/datamate` | `password_type=bcrypt` |
| 组(owner=datamate) | `deepdata-admin` `deepdata-dev` `deepdata-annotator` `deepdata-viewer` | 与 casbin 规则对齐 |
| 用户(owner=datamate) | `dmin`(4 组)、`viewer1`(viewer) | **`tag='DeepData'` 必须与应用 tags 一致**;建议给真实 email |
| 证书 | `admin/cert-built-in` | 应用引用用**裸名** `cert-built-in` |
| 应用 | `admin/DataMate` | clientId `144f4d00553aa7db0aab`;tags `["DeepData"]`;tokenFields `["Groups"]`;`redirectUri=https://deepdata.yhdd.apescale.com:1014/api/user/oauth/callback`;**`expire_in_hours ≥ 1`** |
| Casbin | dmin 4 条 / viewer1 1 条 | 角色→组权限 |
### 踩坑清单(均已踩过,排查先对这里)
1. **`expire_in_hours` 必须 ≥ 1**:应用列名是 `expire_in_hours`(double),不是 token_expiration。为 0 时 `expires_in=0`,id_token `exp=iat`,网关验证报 `JWT expired N milliseconds ago`。
`UPDATE application SET expire_in_hours=1 WHERE owner='admin' AND name='DataMate';`
2. **用户 tag 必须在应用 tags 里**:否则 casdoor 登录报 `User's tag: is not listed in the application's tags`。
3. **`/api/login` 收 JSON body**(不是表单):`{"type":"login","application":"DataMate","username":"dmin","password":"…","organization":"datamate","autoSignin":true}`。
4. **`/api/grant-consent` 的 `application` 必须全名** `admin/DataMate`(裸名报 `Invalid application`);session cookie 与登录**同 host**(cookie 域陷阱)。
5. **authorize 无服务端 302**:带 session 的 GET 只返回 SPA 页面,重定向由前端 `grant-consent` 后完成——脚本化验证直接调 `POST /api/grant-consent`。
## 3. DeepData 侧配置
`deployment/docker/deepdata/.env`(compose 注入,变量名映射见 `docker-compose.yml`):
```ini
DEEPDATA_CASDOOR_ENABLED=true
DEEPDATA_CASDOOR_ISSUER=https://sso.yhdd.apescale.com:1014
DEEPDATA_CASDOOR_AUTHORIZE_URI=http://192.168.31.23:1013/login/oauth/authorize
DEEPDATA_CASDOOR_TOKEN_URI=http://192.168.31.23:1013/api/login/oauth/access_token
DEEPDATA_CASDOOR_JWKS_URI=http://192.168.31.23:1013/.well-known/jwks.json
DEEPDATA_CASDOOR_CLIENT_ID=144f4d00553aa7db0aab
DEEPDATA_CASDOOR_CLIENT_SECRET=***
DEEPDATA_CASDOOR_REDIRECT_URI=https://deepdata.yhdd.apescale.com:1014/api/user/oauth/callback
JWT_SECRET=<不变,本地 JWT 签名密钥>
```
> ISSUER 用公网域名(id_token issuer 断言用);三个 URI 用内网直连(网关→casdoor 同机)。
## 4. 构建与部署
```bash
cd /opt/deepdata/deepdata
make gateway-docker-build frontend-docker-build # 产物 deepdata-{gateway,frontend}:latest
cd deployment/docker/deepdata
docker compose up -d --force-recreate deepdata-gateway deepdata-frontend
```
注意:跳板机链路构建长任务易断连,长构建用 `nohup … > /tmp/gw_build.log 2>&1 &` 后台跑再轮询日志。
同步代码到 31.23 **只按文件同步、禁用 `rsync --delete`**,同步后 `git hash-object` 逐文件核对。
## 5. 验证
```bash
GW_BASE=https://deepdata.yhdd.apescale.com:1014 \
CASDOOR_BASE=http://192.168.31.23:1013 \
SSO_CLIENT_ID=144f4d00553aa7db0aab \
LOCAL_ADMIN_PASSWORD=<本地admin密码> \
SSO_TEST_USERS="dmin:<dmin密码>:admin viewer1:<viewer1密码>:viewer" \
bash scripts/casdoor-sso/e2e-check.sh
```
覆盖:配置公开且无 secret 泄漏 → 本地登录(与 SSO 共存)→ SSO 全链路(authorize→登录→consent→callback→sso_token→/me 角色断言)。
网络注意:公网域名经 222.128.20.101 转发到 31.23:1014,**不要用 `--resolve` 把域名指到 192.168.31.23**(该地址本地未监听);E2E 直接访问公网域名即可。
## 6. 排障
| 现象 | 原因 | 处理 |
|------|------|------|
| `sso_error=id_token_verify` + 网关日志 `JWT expired` | 应用 `expire_in_hours=0` | 见 §2 坑 1 |
| `sso_error=internal` + `provision race … reusing existing user` 后 NPE | 本地 email 列 NOT NULL UNIQUE,空 email 互撞(代码已修为空 email 合成 `username@casdoor.local`) | 升级网关镜像;存量行 `UPDATE users SET email=… WHERE email='';` |
| casdoor 登录 `User's tag: is not listed…` | 用户 tag 不在应用 tags | `UPDATE user SET tag='DeepData' WHERE owner='datamate';` |
| grant-consent `Invalid application` | application 用了裸名 | 用 `admin/DataMate` |
| authorize 返回 200 不跳转 | 正常(SPA 行为);若脚本验证用 grant-consent | — |
| `/sso/casdoor/config` `enabled:false` | `DEEPDATA_CASDOOR_ENABLED` 未注入 | 检查 .env + compose 变量名映射 |