From 8d0eba4957718a3feee595b7716fec879623b24f Mon Sep 17 00:00:00 2001 From: xiaoqu Date: Fri, 4 Sep 2026 17:07:11 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20iOS=E6=AF=8F=E7=94=A8=E6=88=B7Token?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E5=AF=B9=E9=BD=90=E5=B7=B2=E4=BA=A4=E4=BB=98?= =?UTF-8?q?ensure=E6=8E=A5=E5=8F=A3(=E4=B8=A4=E6=AE=B5=E5=BC=8F/=E6=98=8E?= =?UTF-8?q?=E6=96=87=E4=B8=8D=E5=8F=AF=E5=9B=9E=E5=90=90/=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E5=90=8E=E7=AB=AF=E9=97=A8=E9=9D=A2);=20=E4=BF=AEtool?= =?UTF-8?q?s/list=E4=B8=8D=E6=8C=89scope=E8=BF=87=E6=BB=A4=E7=9A=84?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E6=BC=82=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doc/agent-mcp-service-design.md | 2 + .../doc/agent-mcp-tools-reference.md | 2 +- .../xiaoqu-ai-mcp-per-user-token-design.md | 87 +++++++++++-------- 3 files changed, 56 insertions(+), 35 deletions(-) diff --git a/xiaoqu-franchisee/doc/agent-mcp-service-design.md b/xiaoqu-franchisee/doc/agent-mcp-service-design.md index 8235a88..156fb56 100644 --- a/xiaoqu-franchisee/doc/agent-mcp-service-design.md +++ b/xiaoqu-franchisee/doc/agent-mcp-service-design.md @@ -69,6 +69,8 @@ xq CLI ─────┘ (Go 单二进制, 同为 MCP 客户端) ┌── 4. 执行查询,按"人"的数据权限规则加行过滤 5. 审计落库(人、token、工具、入参摘要、结果、耗时) +注:`tools/list` 不按作用域过滤(全量已注册工具对所有 ak 可见),第 3 步的强制点在 `tools/call`——越权调用得到的是可读的拒绝信息 + denied 审计,而不是"工具不存在"。 + ### 3.3 行级数据权限移植规范(照搬现有系统) 来源:`WrapperUtils.getByIsAgent` 系列 + `authority.isagent`。规则: diff --git a/xiaoqu-franchisee/doc/agent-mcp-tools-reference.md b/xiaoqu-franchisee/doc/agent-mcp-tools-reference.md index 3f42652..f881417 100644 --- a/xiaoqu-franchisee/doc/agent-mcp-tools-reference.md +++ b/xiaoqu-franchisee/doc/agent-mcp-tools-reference.md @@ -9,7 +9,7 @@ |---|---| | 端点 | `POST http://:8088/mcp`(MCP Streamable HTTP) | | 认证 | Header `Authorization: Bearer xqak_xxx`,ak 在管理后台「用户与 Token」签发,服务端只存 SHA-256 | -| 工具可见性 | 按 ak 绑定的作用域过滤,`tools/list` 只返回该 ak 可用的工具 | +| 工具可见性 | `tools/list` 返回全部已注册工具(**不按作用域过滤**);作用域在 `tools/call` 时服务端强制校验,越权返回可读拒绝信息并落审计(denied) | | 行级数据权限 | 跟 ak 所属人走,照搬老系统 `getByIsAgent` 语义(见设计文档 §3.3),与作用域无关 | | 分页 | 列表类工具统一 `page`(从 1 开始,默认 1)、`rows`(默认 20,**上限 100**,超出截断为 100 并在返回 `notice` 字段提示) | | 分页出参 | 统一含 `total`(总数)、`page`、`rows`、`records`(本页记录)、`notice`(仅截断时出现) | diff --git a/xiaoqu-franchisee/doc/xiaoqu-ai-mcp-per-user-token-design.md b/xiaoqu-franchisee/doc/xiaoqu-ai-mcp-per-user-token-design.md index 415221e..cf4feb5 100644 --- a/xiaoqu-franchisee/doc/xiaoqu-ai-mcp-per-user-token-design.md +++ b/xiaoqu-franchisee/doc/xiaoqu-ai-mcp-per-user-token-design.md @@ -46,7 +46,7 @@ 2. 当前 FFI 将 `apiKey` 发送为 `X-API-Key`,与服务端要求的 Bearer 不一致。 3. iOS FFI 未把配置文件中的 MCP Server 自动接入 `FfiEngineConfig`。 4. 多 MCP Client 共存时,工具调用没有稳定的“工具名 → 所属 Server”映射。 -5. 当前管理端签发接口需要管理员会话,不适合作为普通用户登录后的自动取 Token 接口。 +5. ~~当前管理端签发接口需要管理员会话,不适合作为普通用户登录后的自动取 Token 接口。~~ **已解决(2026-09-04)**:xiaoqu-agent-service 已交付 Open API `POST /api/open/token/ensure`(get-or-create,服务间密钥鉴权),见 §4.1。 ## 3. 方案设计 @@ -70,46 +70,56 @@ | 退出登录 | 销毁旧 Claw/MCP Client,删除当前账号本地 MCP Token;服务端撤销或等待短期过期 | | 多账号 | Keychain key 必须包含稳定用户 ID,禁止所有账号共用 `mcpToken` | -建议 MCP Token 默认有效期 15 分钟,并提供换取接口;长期 AK 仅用于受控开发/管理场景。 +~~建议 MCP Token 默认有效期 15 分钟,并提供换取接口;长期 AK 仅用于受控开发/管理场景。~~ + +**2026-09-04 更新(与已交付实现对齐)**:现行模型为**长期有效 + 禁用回收**(与管理后台签发模型一致),不做 15 分钟短周期。`ensure` 语义天然兼容短周期模式(过期 token 不计入"已存在",会触发新建并返回新明文),将来如需缩短有效期,只调整签发时的 `expireTime`,协议不变。 ## 4. API 契约 -### 4.1 POST `/api/v1/mcp/session-token` +> **2026-09-04 更新**:本节按 xiaoqu-agent-service 已交付的 Open API 重写。核心变化——agent-service 不直接验证 xiaoqu-ai 登录态(它没有 JWT 基础设施,也不懂租户),改为**两段式**:iOS 用登录态调 xiaoqu-ai 业务后端;业务后端拿着登录用户的手机号/邮箱,用服务间密钥调 agent-service 的 `ensure` 接口取 MCP Token。 -描述: 使用当前 xiaoqu-ai 登录态,为当前用户签发一个短期 MCP Token。 +### 4.1 服务端原语(已交付):POST `/api/open/token/ensure` + +描述: 按 手机号/邮箱 + 作用域编码 确保用户有有效 MCP Token(get-or-create)。 认证: -- `Authorization: Bearer ` -- 服务端必须从签名 Token 解析用户和租户,忽略请求体中的身份字段。 +- `X-Open-Key: <服务间静态密钥>`(dev 配置在 `application-dev.yml` 的 `app.open.api-key`,生产走环境变量;配置为空 = 接口关闭)。 +- 只允许 xiaoqu-ai 业务后端等服务端调用方持有,**不得下发到 App**。 请求体: -- `client`: string (required) — 固定为 `xiaoqu-ai-ios`,最长 64 字符。 -- `device_id`: string (required) — 设备安装实例 ID,最长 128 字符;不得包含 Token。 +- `mobile`: string — 登录用户手机号;与 `email` 至少传一个,手机号优先。 +- `email`: string — 登录用户邮箱。 +- `scopeCode`: string (required) — 作用域编码(如 `operation`);iOS 用哪个作用域由双方约定后在此固定。 响应 200: -- `token`: string (required) — MCP 明文 Token,仅本次返回。 -- `expires_at`: datetime (required) — MCP Token 到期时间。 -- `user_id`: int (required) — 服务端从登录态解析出的用户 ID。 -- `tenant_id`: int (required) — 服务端解析出的租户 ID。 -- `scope_version`: string (required) — 本次绑定权限版本,用于客户端诊断,不作为鉴权依据。 +- `created`: bool — 本次是否新建。 +- `token`: string | null — **仅 `created=true` 时返回明文,且只出现这一次**;`created=false` 时恒为 null。 +- `keyPrefix`: string — Token 前缀(如 `xqak_abcd1234`),用于标识和对账。 +- `expireTime`: datetime | null — 现行为 null(永久,靠禁用回收)。 +- `userId` / `userName` / `scopeCode`: 归属信息。 -错误: +错误(统一 `{"message": "原因"}`): -- `401`: 登录态缺失、无效或已过期。 -- `403`: 用户无权使用 MCP 或租户未开通。 -- `409`: 用户 Token 配置缺失/作用域未配置。 -- `429`: 换取频率超限。 -- `503`: MCP Token 服务暂不可用;客户端不得携带旧 Token 强行重试写操作。 +- `400`: 参数缺失 / 作用域不存在或已停用。 +- `401`: `X-Open-Key` 无效。 +- `403`: Open API 未启用(服务端未配置密钥)。 +- `404`: 人员不存在或非企业账号(只认 `enterprise_type` 0/1 的企业账号)。 -服务端处理要求: +服务端处理(已实现): -- 使用现有 `agent_api_key` 哈希存储和 `token_scope` 绑定逻辑。 -- 每次签发生成新的随机 Token,明文仅返回一次;不能查询旧 Token 明文。 -- 默认只绑定当前用户允许的只读作用域;高风险作用域必须单独审批。 -- 签发、刷新、撤销写入审计日志,记录 `user_id`、`tenant_id`、client、device_id、结果和原因,不记录 Token。 +- 复用 `agent_api_key` 哈希存储与 `token_scope` 绑定;一作用域一 Token。 +- 已存在有效 Token(正常且未过期)只回元信息——**明文不可回吐,服务端只存 SHA-256**。 +- 不存在则自动创建永久 Token,memo 标记 `open-api 自动签发()`。 +- 全程写 `audit_log`(`tool_name=open.token.ensure`、`client_type=open-api`,手机号/邮箱脱敏,不记录 Token)。 + +**关键约束与对策**:`created=false` 时拿不到明文。因此 xiaoqu-ai 业务后端必须在首次 `created=true` 时**自己持久化明文**(按 user_id 关联,加密存储),之后下发给本用户的客户端;若明文丢失,只能由管理员在后台禁用旧 Token 后再调 `ensure` 触发新建(或将来加 rotate 接口)。 + +### 4.1b xiaoqu-ai 业务后端门面(待 xiaoqu-ai 侧实现) + +iOS 面向的接口(建议沿用原设计):POST `/api/v1/mcp/session-token`,`Authorization: Bearer `。业务后端从登录态解析用户 → 取手机号/邮箱 → 调 `ensure` → 返回 `{token, expires_at, user_id}`。客户端身份字段(client、device_id)由业务后端自行记录,agent-service 不感知。 ### 4.2 POST `/mcp` @@ -184,6 +194,8 @@ Token 不写入该文件。客户端运行时读取当前用户 Token,并动 ### 6.2 建议新增字段(仅在现有字段不足时) +> 2026-09-04 注:已交付实现暂不新增字段——ensure 自动签发的 Token 用 `memo` 标记(`open-api 自动签发()`),`create_id=0` 表示系统签发。若后续需要按来源/设备撤销或统计,再按下表加独立 migration。 + | 表 | 字段 | 类型/约束 | 说明 | |---|---|---|---| | `agent_api_key` | `source` | `VARCHAR(32) NOT NULL DEFAULT 'admin'` | `admin` / `user_session`,便于撤销和审计 | @@ -197,12 +209,13 @@ Token 不写入该文件。客户端运行时读取当前用户 Token,并动 | 文件/目录 | 层级 | 类型 | 改动说明 | |---|---|---|---| -| `security/` JWT 验证组件 | Security | 新增/修改 | 验证 xiaoqu 登录态并解析 user/tenant,拒绝客户端自报身份 | -| `mcp/` Token exchange service | Service | 新增 | 签发短期用户 MCP Token、作用域绑定、限流和审计 | -| `handler/` MCP credential controller | Handler | 新增 | 实现 `/api/v1/mcp/session-token` | -| `security/AkAuthFilter.java` | Filter | 修改 | 保持 Bearer MCP AK 兼容,补齐撤销/错误审计要求 | -| `db/migration/V__user_session_token.sql` | Migration | 条件新增 | 只有字段/索引确有需要时新增 | -| `src/test/...` | Test | 新增 | 用户隔离、过期、撤销、越权、限流和不记录明文测试 | +| ~~`security/` JWT 验证组件~~ | Security | ~~新增~~ 取消 | agent-service 不验证 xiaoqu-ai 登录态,改由业务后端两段式调用 | +| `open/OpenTokenController.java` 等 | Open API | **已交付**(2026-09-04) | `POST /api/open/token/ensure`,X-Open-Key 鉴权,get-or-create | +| `open/OpenKeyFilter.java` | Filter | **已交付** | 服务间静态密钥,常量时间比较,配置为空即关闭 | +| `admin/AdminTokenService.java` | Service | **已交付** | 一作用域一 Token;`issueSingle`/`findActiveToken` 供 ensure 复用 | +| `common/GlobalExceptionHandler.java` | Handler | **已交付** | 错误统一 `{"message": ...}` | +| `security/AkAuthFilter.java` | Filter | 保持兼容 | `/mcp` Bearer ak 校验不变 | +| `src/test/...open` | Test | **已交付** | ensure 已存在/新建/404/停用作用域/缺参 5 个用例 | ### xiaoqu-ai @@ -220,8 +233,8 @@ Token 不写入该文件。客户端运行时读取当前用户 Token,并动 | # | 任务标题 | 工时 | 依赖 | 完成标准 | |---|---|---:|---|---| -| 1 | 服务端确认 JWT 与租户映射、Token exchange 契约 | 2h | - | 401/403/409/429 语义和 scope 默认值确定 | -| 2 | 服务端实现用户 MCP Token 签发与审计 | 4h | #1 | Token 只返回一次,哈希落库,绑定 user/tenant/scope | +| 1 | ~~服务端确认 JWT 与租户映射、Token exchange 契约~~ → xiaoqu-ai 业务后端实现门面接口(登录态 → ensure) | 2h | - | 门面 401/403 语义确定;明文按 user_id 加密落库 | +| 2 | ~~服务端实现用户 MCP Token 签发与审计~~ **已交付**(ensure + 审计,2026-09-04) | - | - | 已上线并实测幂等/脱敏/错误语义 | | 3 | iOS Keychain 账号隔离和认证生命周期接入 | 3h | #1 | 登录/切换/退出无旧 Token 残留 | | 4 | Rust MCP HTTP Bearer、Session、初始化通知 | 3h | #3 | initialize/list/call 真实握手成功 | | 5 | MCP 工具到 Server 路由映射 | 2h | #4 | 多 Server 下工具调用不发错目标 | @@ -250,8 +263,14 @@ Token 不写入该文件。客户端运行时读取当前用户 Token,并动 ### 9.3 联调命令 ```bash -# 使用当前用户登录态换 MCP Token(示例,不把 Token 写入仓库) -curl -X POST http:///api/v1/mcp/session-token \ +# 业务后端侧:用服务间密钥 ensure 用户 MCP Token(示例,密钥与明文不写入仓库) +curl -X POST http://:8088/api/open/token/ensure \ + -H 'X-Open-Key: <服务间密钥>' \ + -H 'Content-Type: application/json' \ + -d '{"mobile":"<登录用户手机号>","scopeCode":"operation"}' + +# iOS 侧:走 xiaoqu-ai 业务后门门面(待实现) +curl -X POST http:///api/v1/mcp/session-token \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"client":"xiaoqu-ai-ios","device_id":""}'