Archived
文档:两份前端文档按真环境核对 —— clientId 实测为 task-dispatcher,示例换成实测响应
设计阶段写的两份对外文档,Task 12 只按契约核过,没按真环境核过。这次跑通之后逐条对: ① clientId 写的是「建议 api-gateway」——实际这个 realm 里 api-gateway 的 mapper 对我们不适用, 本部署是 task-dispatcher(两个前端 client 都已挂好指向它的 audience mapper,实测确认); ② e2e 账号写的是 bpos-e2e,实际用的是 task-dispatcher 的服务账号,且它只有 service+user 两个角色 → 只进得了工作台,要跑测试台得另配; ③ 「ends 为空」原本像个假想边界,实际 service 角色就是——没配进「角色→端」表的角色一律不加端; ④ §8 的响应示例从「按契约构造」换成实测响应(/api/me 的 JIT 建档结果、403 的真 message、 aud 不对时的 401、票据的真形状); ⑤ 两处「核对状态」改写:前置已就位、整条链真环境验过,并写明仍未实测的那一件——用真人 token 走一遍。
This commit is contained in:
+30
-8
@@ -9,7 +9,7 @@
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| Base | 同源 `/api/*`(开发期 Vite 把 `/api` 反代到 `:8787`) |
|
||||
| 认证 | `Authorization: Bearer <Keycloak access_token>`;RS256;`iss` = `https://keycloak.akhr-ai.com/realms/corp`;`aud` 须含本服务 clientId(`KEYCLOAK_CLIENT_ID`,建议 `api-gateway`) |
|
||||
| 认证 | `Authorization: Bearer <Keycloak access_token>`;RS256;`iss` = `https://keycloak.akhr-ai.com/realms/corp`;`aud` 须含本服务 clientId —— **本部署是 `task-dispatcher`**(后端环境变量 `KEYCLOAK_CLIENT_ID`) |
|
||||
| 票据 | **GET** 且没带头时认 `?ticket=<60 秒票据>`(取自 `GET /api/auth/ticket`);其它方法不认 |
|
||||
| HEAD | 按 GET 判(框架把 HEAD 改写成 GET 再分发)—— 端归属、票据两处都与同路径的 GET 一模一样 |
|
||||
| 请求体 | JSON,`content-type: application/json` |
|
||||
@@ -367,24 +367,41 @@ type ApiError = { code: 'unauthenticated' | 'forbidden' | 'not-found' | 'validat
|
||||
|
||||
## 8. curl 实录
|
||||
|
||||
用服务账号拿 token(自动化的正道,手册 §10;该 client 需 `serviceAccountsEnabled`、realm 角色 `admin`、且 audience mapper 指向本服务 clientId):
|
||||
用服务账号拿 token(自动化的正道,手册 §10;该 client 需 `serviceAccountsEnabled`,且 audience mapper **指向本服务 clientId** ——
|
||||
服务账号 token 默认不含自己的 clientId,漏了这条就会 401 并点名让你加 mapper):
|
||||
|
||||
```bash
|
||||
TOKEN=$(curl -s -X POST https://keycloak.akhr-ai.com/realms/corp/protocol/openid-connect/token \
|
||||
-d grant_type=client_credentials -d client_id=bpos-e2e -d client_secret="$KEYCLOAK_E2E_CLIENT_SECRET" | jq -r .access_token)
|
||||
-d grant_type=client_credentials -d client_id=task-dispatcher -d client_secret="$KEYCLOAK_E2E_CLIENT_SECRET" | jq -r .access_token)
|
||||
|
||||
# 我是谁
|
||||
# 我是谁 —— 下面是 2026-08-26 的实测响应(服务账号首次调用即被 JIT 建档,落「未分配」岗)
|
||||
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8787/api/me | jq .
|
||||
# {
|
||||
# "staff": { "id": "49a85fc7-020c-4098-ba38-af7b138129ea",
|
||||
# "account": "service-account-task-dispatcher",
|
||||
# "name": "service-account-task-dispatcher",
|
||||
# "positionId": "3e36f2dd-982b-4412-bb1a-4c12a93c053d",
|
||||
# "state": "active", "roles": ["service", "user"] },
|
||||
# "position": { "id": "3e36f2dd-…", "code": "NONE", "name": "未分配",
|
||||
# "note": "未指派岗位;流程包不得声明它" },
|
||||
# "roles": ["service", "user"],
|
||||
# "ends": ["workbench"] ← service 没配进「角色→端」表,只有 user 生效
|
||||
# }
|
||||
|
||||
# 没带 token → 401
|
||||
curl -s http://localhost:8787/api/me | jq .
|
||||
# { "code": "unauthenticated", "message": "缺少 Bearer token" }
|
||||
|
||||
# 端不够 → 403(拿一个只有 user 角色的 token 试)
|
||||
curl -s -H "Authorization: Bearer $USER_TOKEN" http://localhost:8787/api/gov/tasks | jq .
|
||||
# { "code": "forbidden", "message": "进入「运营中心」需要授权;你的角色(user)可进:工作台", "detail": { "need": ["ops"], "ends": ["workbench"] } }
|
||||
# 端不够 → 403(实测响应)
|
||||
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8787/api/gov/tasks | jq .
|
||||
# { "code": "forbidden", "message": "进入「运营中心」需要授权;你的角色(service, user)可进:工作台", "detail": { "need": ["ops"], "ends": ["workbench"] } }
|
||||
|
||||
# aud 不对 → 401,话直接点到要改哪儿(实测响应:mapper 补上之前就是这个)
|
||||
# { "code": "unauthenticated", "message": "token 的 aud 不含 \"task-dispatcher\" —— 去 Keycloak 给发 token 的那个 client 加 audience mapper" }
|
||||
|
||||
# 票据 + SSE
|
||||
# 实测:{ "ticket": "v1.NDlhODVmYzctMDIwYy00MDk4LWJhMzgtYWY3YjEzODEyOWVh.1787712844.x6HoZb7E…",
|
||||
# "expiresAt": "2026-08-26T02:54:04.000Z" } ← 中段是 base64url 的 sub,末段是 HMAC
|
||||
TICKET=$(curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8787/api/auth/ticket | jq -r .ticket)
|
||||
curl -s -N "http://localhost:8787/api/stream?ticket=$TICKET"
|
||||
# event: ready
|
||||
@@ -406,4 +423,9 @@ curl -s -X POST http://localhost:8787/api/login | jq .
|
||||
```
|
||||
|
||||
Task 12 已用契约(`packages/contracts/src/api/*.ts` + `org.ts`)核过本文:§6 总表逐行比对生成表、§7 字段名逐个核对 schema —— 全部一致,零改动。
|
||||
示例里的 UUID / 岗位 id / 时间戳仍按契约构造 —— 真实响应需要 Keycloak 侧前置 4(e2e / 试跑服务账号 client `bpos-e2e`:`serviceAccountsEnabled`、realm 角色 `admin`、挂本服务 audience mapper)先就位,这是管理员的活,本轮未就位;前置就位后用 §8 给的 curl 序列换成真实响应。
|
||||
**2026-08-26 补:已在真环境跑通,§8 的响应示例换成了实测响应。** 当时的部署事实:
|
||||
`KEYCLOAK_CLIENT_ID=task-dispatcher`;`task-dispatcher` / `web-portal` / `web-console` 三个 client 都挂了指向
|
||||
`task-dispatcher` 的 audience mapper;realm 里 8 个用户,5 个持 `admin`(→ 三端)、3 个持 `user`(→ 工作台)。
|
||||
实测验过:验签 · claims 归一(噪声角色已滤)· JIT 建档 · 角色→端 · 端判定(工作台 200 / 运营中心 403)·
|
||||
票据签发与 SSE 连通 · 票据在 POST 上被拒 · `HEAD` 与 `GET` 判定一致 · 退役路由 410。
|
||||
仍未实测的只有一件:**用真人 token 走一遍**(要前端接入,或临时开 Direct Access Grants,手册 §6.1)。
|
||||
|
||||
Reference in New Issue
Block a user