Archived
文档:ADR-012 统一鉴权中心;编码规范鉴权定调改为资源服务器 + 权限只到端;org CONTRACT.md 重写;debt 鉴权段;CLAUDE.md 铁规矩 5/6
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
# ADR-012 统一鉴权中心(Keycloak):身份、名册与「角色 → 端」
|
||||
|
||||
状态:已接受(2026-08-25)
|
||||
背景出处:`docs/keycloak-backend-guide.md`(Keycloak 26.7.1 / jose,核实日期 2026-08-12);
|
||||
设计稿 `docs/superpowers/specs/2026-08-25-keycloak-auth-design.md`。
|
||||
取代:ADR-010(口令 + 服务端会话)与编码规范 §4 原「不引 OAuth/OIDC」的定调。
|
||||
|
||||
## 问题
|
||||
|
||||
公司有了统一鉴权中心,本系统还在自建账号口令、服务端会话表与岗位授权(客体三类 × 动作三种)。
|
||||
人要在两处维护,权限模型也比产品实际需要的细太多 —— 用户只要「不同角色可以动态配置能进哪几个端」。
|
||||
|
||||
## 决策
|
||||
|
||||
1. **资源服务器,不做 BFF**:前端(`web-portal` / `web-console`,PKCE)自取 token;后端只本地验签
|
||||
Bearer JWT(`jose`:RS256 写死、校 iss 与 aud、30s 容差、远程 JWKS 缓存 10 分钟 / 冷却 30 秒),
|
||||
不回源、不 introspect、**不配 client secret**。
|
||||
2. **身份在 token 里**:`Staff.id` = `sub`;`account` = `preferred_username` 只作显示;
|
||||
首次见到即建档(JIT),账号 / 姓名 / 邮箱 / 角色变了才写,`last_seen_at` 60 秒内不重写。
|
||||
3. **权限只到端**:三个端 design / ops / workbench;`sys.role_access` 一行一个 realm 角色,
|
||||
运营中心可改;一个人的端 = 其角色命中行的并集。缺省 `admin → 三端`,`user → workbench`。
|
||||
**锁死不变量**:改完至少一名在册活跃员工仍能进运营中心,否则 409。
|
||||
4. **路由归属进契约**:`RouteContract.access` 必填(public / any / 端 / 端数组);app 层一处中间件
|
||||
按注册表判 401 / 403;治理路由里约 100 处逐条守卫整体删除;覆盖测试 fail-closed。
|
||||
5. **名册**:岗位仍是本系统的表(哨兵岗 `NONE / 未分配`,运营中心指派);名册经 Admin API 同步
|
||||
(master realm 的 provisioning 服务账号,只要 `view-users`)+ 首登即建。`state` 只由同步置
|
||||
`disabled`(Keycloak 停用或删除),有效 token 不改它 —— 撤权在同步之后即时 403。
|
||||
6. **票据**:`EventSource` 与 `<a href download>` 带不了 Authorization 头 —— `GET /api/auth/ticket`
|
||||
换一枚 60 秒 HMAC 票据(`HKDF(JCAI_SECRET_KEY, 'jcai-ticket')`),只在 GET 的 `?ticket=` 上认;
|
||||
票据里没有权限,中间件拿 sub 回库现算。无状态、不单次:60 秒重放窗换零状态表。
|
||||
7. **退役**:`login` / `logout` / `session` / 建人 / 停用 / 岗位授权八条路由回 410(新错误码 `gone`),
|
||||
契约标 `@deprecated` 留一轮(§12.4 三步);`sys.session` `sys.position_grant` `staff.password_hash` drop。
|
||||
8. **测试账号**:`JCAI_TEST_ACCOUNTS`(Keycloak 用户名)命中者落 TEST 岗 + 独立工作组(ADR-011 原语义);
|
||||
e2e 与试跑用服务账号(client_credentials),不用人的账号。
|
||||
|
||||
## 后果
|
||||
|
||||
- 撤角色的时效 ≤ 5 分钟(token 寿命);停用经同步后即时。
|
||||
- web 需按《前端接入手册》改造(登录 / Bearer / 401 跳转 / SSE 与下载改走票据 / 入口亮灯改读 `/api/me` 的 `ends`),
|
||||
改造前登不进 —— 这是「只做后端」的既定后果。
|
||||
- **部署顺序是先跑迁移、再 `reset-runs`**:迁移 0032 清空 `sys.staff` 并连带清掉两张持有身份的
|
||||
**配置**表(`sys.node_owner`、`sys.trigger` 的确认位);而 `sys.task.owner_id` 这类**无外键**的
|
||||
运行数据列不受迁移约束,若顺序颠倒,这些运行数据会指着一个已经不存在的身份变成孤儿 ——
|
||||
`reset-runs` 必须排在迁移之后跑,才能把这批指着旧身份的运行数据一并清掉。
|
||||
- Keycloak 侧前置(管理员):本服务 client(建议复用 `api-gateway`)、`web-console` 补 audience mapper、
|
||||
provisioning 服务账号、e2e 服务账号 client(`bpos-e2e`,realm 角色 admin,firstName「AI」)。
|
||||
- 欠账见 `docs/debt.md`「鉴权」段。
|
||||
+15
-7
@@ -56,14 +56,22 @@ ADR-011 的「绑定先于策略」随岗位一起退场 —— 工作组快照
|
||||
`.parallel()` 任一失败即整体失败(失败走向不进 Mastra)、三态聚合投影
|
||||
(读实例表不读 run 状态)、`node_instance` 投影表照写(发号权在主工程)。
|
||||
|
||||
## 鉴权(ADR-010 落地后的已知欠账)
|
||||
## 鉴权(ADR-012 落地后的已知欠账)
|
||||
|
||||
- **登录失败无次数限制**:可无限次试口令。单组织 ≤50 人、内网部署下暂缓;
|
||||
接入外网前必须补(按账号 + 来源 IP 双维度退避)。
|
||||
- **无口令强度校验、无首登强制改密**:全员初始口令相同(`JCAI_SEED_PASSWORD`),
|
||||
谁都没改过就等于没有口令。治理台「员工」面需补:改密 / 重置 / 强制改密标记。
|
||||
- **`setPassword` 已在 org 出口就位但无路由**:改密只能经播种或直接调用例,界面上没有入口。
|
||||
- **会话无「登出全部设备」**:`revokeAllOf` 已实现(停用员工时调),但本人无从触发。
|
||||
- **撤角色时效 ≤ 5 分钟**:改了 Keycloak 里的角色,现有 access token 到期前仍按旧角色算(手册 13.4);
|
||||
要立即生效在 Keycloak 上 `POST /users/{id}/logout`。停用经名册同步后即时 403。
|
||||
- **票据 60 秒可重放窗**(无状态换来的):绑定 sub、只认 GET;要单次得加一张已用票据表。
|
||||
- **名册同步是手动按钮**(运营中心 · 员工 · 同步),没有定时;没配 provisioning 就只有首登即建。
|
||||
- **web 待按《Keycloak 前端接入手册》接入**:登录页 / Bearer / 401 跳转 / SSE 与下载改走 `GET /api/auth/ticket` /
|
||||
入口亮灯改读 `GET /api/me` 的 `ends`;接入前登不进。那一刻一并清掉 `SURFACE_FNS` 与退役契约。
|
||||
- **退役契约下个迭代删**(§12.4 三步之第三步):`loginRoute` `logoutRoute` `sessionRoute` `createStaffRoute`
|
||||
`disableStaff*Route` `listGrantsRoute` `setGrantsRoute` 与 `sessionSchema` / `grant*` / `governanceFunction*` / `disableCheckSchema`。
|
||||
- **`groups` 未用**:日后要按部门过滤数据,需 Keycloak 配 group membership mapper。
|
||||
- **aud mapper 未配即全线 401**:401 的 message 会点名「给发 token 的那个 client 加 audience mapper」,但没有启动期探测。
|
||||
- **无库环境里,路由覆盖的 fail-closed 保险会静默消失**:`apps/api/src/app/__tests__/route-coverage.it.test.ts`
|
||||
用 `describe.skipIf(database === undefined)` 跳过(同目录另外三份 it-test 同款,是仓库级惯例)——
|
||||
没有库的环境里这条测试根本不跑,进程仍 exit 0;正在搭 CI 的人必须先给它一个库,
|
||||
否则「写了路由处理器却没在契约里声明 access」这类漏洞会在 CI 里悄悄放过。
|
||||
|
||||
## 工作组(ADR-011 落地后的已知欠账)
|
||||
|
||||
|
||||
+3
-1
@@ -44,6 +44,7 @@
|
||||
| 台账自生长 | node-postgres(`pg`)直写(ledger adapter 内) | 运行时 DDL/DML 由声明生成,ORM 静态 schema 不适用 |
|
||||
| 队列/定时 | pg-boss | PG 原生,先持久后投递、至少一次天然契合 |
|
||||
| 代码契约校验 | zod | 仓库内 API/DTO |
|
||||
| 鉴权 | Keycloak(统一鉴权中心)+ `jose` 本地验签 | 资源服务器:RS256 / iss / aud 本地验,不回源;权限只到端(ADR-012) |
|
||||
| 声明数据校验 | ajv(JSON Schema 2020-12 子集) | 活动声明是运行时数据,不是编译期代码(ADR-003) |
|
||||
| 测试 | Vitest;集成测试用 Testcontainers(PG) | domain 层测试零 mock |
|
||||
| 门禁 | tsc / ESLint(flat) / dependency-cruiser / 契约快照 | §11 |
|
||||
@@ -154,7 +155,7 @@ repo/
|
||||
- 技能编排属于活动(AM:活动内容物含技能清单)——`skill-engine` 与 loop/flow 同列范式运行器;置于 L4 因两范式依赖它(临场装配/步引用),import 恒向下、免 port。
|
||||
- 找不到归属的代码,先怀疑模块拆分错了,再考虑 `shared/`;`shared/` 只收无业务语义的纯工具
|
||||
- 度量八指标(OS 8.3)是 `memory`(任务日志)与 `task`(终态/纠正信号)之上的**只读投影**,落 `report` 的用例,不设独立模块
|
||||
- 鉴权定调:单组织 cookie session——登录、会话与岗位权限校验中间件在 apps/api 的 app 层实现,权限判定读 `org` 出口;不引 OAuth/OIDC 与多租户机制
|
||||
- 鉴权定调(ADR-012):统一鉴权中心(Keycloak)资源服务器——后端只验 Bearer JWT;鉴权中间件在 apps/api 的 app 层实现,身份判定读 `org` 出口;**权限只到端**(设计中心 / 运营中心 / 工作台),角色→端由运营中心的表定,路由归属写在契约的 `access` 上;不做多租户
|
||||
|
||||
---
|
||||
|
||||
@@ -640,6 +641,7 @@ pnpm test
|
||||
- **ADR-004** 活动 .JS 装载 ABI:单文件 ESM,默认导出 `defineActivity()`;能力全部注入(基座六件句柄),零环境能力;静态审扫描产物 import 与全局引用
|
||||
- **ADR-005** 工具 `validate` 能力位:调用契约 `capabilities.validate?: true`,预检存在即调(OS 7.3 落地预检第四项)
|
||||
- **ADR-006** S-FEEL 自研于 `@sfeel`,子集文法冻结于 OS 03;不引第三方 FEEL 引擎
|
||||
- **ADR-012** 统一鉴权中心(Keycloak):资源服务器本地验签(`jose`),`Staff.id` = `sub`;权限只到端,`sys.role_access` 可改;名册 Admin API 同步 + 首登即建;60 秒票据给 SSE 与下载;口令 / 会话 / 岗位授权退役(取代 ADR-010)
|
||||
|
||||
留白(实现期再定,各建 ADR 后方可动工):鉴权会话与凭证加密方案;JSON Schema→zod 转换库选型;活动装载的 worker 隔离硬化;活动记忆向量召回启停;e2e 框架。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user