From 8af60565a1b41ba9c748ead73818e33a2b9dae6e Mon Sep 17 00:00:00 2001 From: xqzp2026 Date: Mon, 7 Sep 2026 13:04:13 +0930 Subject: [PATCH] docs: sync MCP token spec to one-time mobile token flow --- .../doc/agent-mcp-service-design.md | 7 ++--- .../doc/agent-mcp-tools-reference.md | 2 +- .../xiaoqu-ai-mcp-per-user-token-design.md | 28 +++---------------- .../doc/xiaoqu-mcp-integration-setup.md | 9 ++++-- 4 files changed, 14 insertions(+), 32 deletions(-) diff --git a/xiaoqu-agent-service/doc/agent-mcp-service-design.md b/xiaoqu-agent-service/doc/agent-mcp-service-design.md index 13b4f1c..2f3f929 100644 --- a/xiaoqu-agent-service/doc/agent-mcp-service-design.md +++ b/xiaoqu-agent-service/doc/agent-mcp-service-design.md @@ -123,7 +123,7 @@ xq CLI ─────┘ (Go 单二进制, 同为 MCP 客户端) ┌── ``` - Claude Code:`claude mcp add --transport http xiaoqu https://mcp.<域名>/mcp --header "Authorization: Bearer "`。 -- ak 明文只出现在本机客户端配置与签发时刻;服务端只存 SHA-256 哈希,含 ak 的客户端配置文件不得提交版本库。 +- ak 明文只出现在受信任服务间响应和运行时;服务端保存 SHA-256 哈希及加密密文,含 ak 的客户端配置文件不得提交版本库。 ### 5.2 Web 管理后台(四个页面) @@ -155,7 +155,7 @@ xq CLI ─────┘ (Go 单二进制, 同为 MCP 客户端) ┌── - **`POST /api/open/token/ensure`**(get-or-create): - 入参:`mobile`、`email`(至少一个,手机号优先)、`scopeCode`(作用域编码,必填)。 - 人员只认企业账号(`enterprise_type` 0/1,NULL 保留,与后台用户列表同口径);查不到 → 404。 - - 该人员在该作用域下**已存在**有效 Token(正常且未过期)→ `created=false`,只回 `keyPrefix`/`expireTime` 等元信息。**明文不可回吐**:服务端只存 SHA-256,这是安全红线,调用方丢了明文只能走后台重新签发。 + - 该人员在该作用域下**已存在**有效 Token(正常且未过期)→ `created=false`,只回 `keyPrefix`/`expireTime` 等元信息;受信任应用可另调 `POST /api/open/token/get` 二次获取解密后的明文。 - **不存在** → 自动创建永久 Token(一作用域一 Token,memo 标记 `open-api 自动签发`),`created=true` 返回明文,仅此一次。 - 全程写 `audit_log`(`tool_name=open.token.ensure`,`client_type=open-api`,参数摘要脱敏)。 @@ -240,7 +240,7 @@ xiaoqu-agent-service/ - 不依赖老 jar,Entity 自行生成 → 与老系统零代码依赖 - 前后端一体打包单 jar → 可逆决定,将来可拆 - 一作用域一 Token(2026-09-04)→ 收回某能力不需整把吊销;数据模型不变(token_scope 每把只写一行) -- Open API 已存在 Token 只回元信息不回明文 → 服务端只存哈希,明文不可回吐是安全红线 +- 长期 Token 需要二次获取 → 服务端保存哈希和加密密文,`token/get` 仅对白名单服务开放并全程审计 - Open API 鉴权用配置文件静态密钥(dev)→ 服务间调用方少,落库+轮换留给生产阶段 - 列表类接口一律服务端分页,pageSize/rows 上限 100(超出截断为 100)→ 防止一次性拉全表,MCP 工具与后台 API 同一尺度 @@ -266,4 +266,3 @@ xiaoqu-agent-service/ - 合并回 main 后用 `xiaoqu-agent-service/deploy/deploy.sh restart` 完成构建+重启,接口实测通过再 `git push`。 - 后台列表页统一服务端分页(page 从 1 开始,pageSize 默认 20、上限 100);MCP 工具 `rows` 同样上限 100,超出截断并在 `notice` 字段提示。 - **新增/修改 MCP 工具必须同步更新 `doc/agent-mcp-tools-reference.md`**(入参、出参、权限语义、示例),工具对外契约以该文档为准。 - diff --git a/xiaoqu-agent-service/doc/agent-mcp-tools-reference.md b/xiaoqu-agent-service/doc/agent-mcp-tools-reference.md index 3466b2c..fd14202 100644 --- a/xiaoqu-agent-service/doc/agent-mcp-tools-reference.md +++ b/xiaoqu-agent-service/doc/agent-mcp-tools-reference.md @@ -8,7 +8,7 @@ | 项 | 说明 | |---|---| | 端点 | `POST http://:8088/mcp`(MCP Streamable HTTP) | -| 认证 | Header `Authorization: Bearer xqak_xxx`,ak 在管理后台「用户与 Token」签发,服务端只存 SHA-256 | +| 认证 | Header `Authorization: Bearer xqak_xxx`,Token 由管理后台或受信任 Open API 获取;服务端保存 SHA-256 及加密密文 | | 工具可见性 | `tools/list` 按 ak 作用域过滤,只返回该 ak 作用域并集内且启用状态的工具(2026-09-04 起,服务端 servlet 层重写响应实现);`tools/call` 另有强制校验兜底,越权返回可读拒绝信息并落审计(denied) | | 行级数据权限 | 跟 ak 所属人走,照搬老系统 `getByIsAgent` 语义(见设计文档 §3.3),与作用域无关 | | 分页 | 列表类工具统一 `page`(从 1 开始,默认 1)、`rows`(默认 20,**上限 100**,超出截断为 100 并在返回 `notice` 字段提示) | diff --git a/xiaoqu-agent-service/doc/xiaoqu-ai-mcp-per-user-token-design.md b/xiaoqu-agent-service/doc/xiaoqu-ai-mcp-per-user-token-design.md index 566ce8a..e70e979 100644 --- a/xiaoqu-agent-service/doc/xiaoqu-ai-mcp-per-user-token-design.md +++ b/xiaoqu-agent-service/doc/xiaoqu-ai-mcp-per-user-token-design.md @@ -115,29 +115,8 @@ - 不存在则自动创建永久 Token,memo 标记 `open-api 自动签发()`。 - 全程写 `audit_log`(`tool_name=open.token.ensure`、`client_type=open-api`,手机号/邮箱脱敏,不记录 Token)。 -**关键约束与对策**:`created=false` 时拿不到明文。因此 xiaoqu-ai 业务后端必须在首次 `created=true` 时**自己持久化明文**(按 user_id 关联,加密存储),之后下发给本用户的客户端。 - **身份约束**:业务后端不得接受客户端或模型传入的 `user_id`、`mobile`、`email` 作为身份依据。必须先从已验证的 xiaoqu-ai `accessToken` 会话解析用户,再由业务后端查询该用户的手机号/邮箱并调用 `ensure`。`X-Open-Key` 只允许服务端保管。 -### 4.1a Token 丢失与主动失效(建议补充) - -当前 `ensure` 在 `created=false` 时不会返回明文,无法解决业务后端首次落库失败或 Token 泄露后的换发问题。建议增加两个服务间接口,均使用 `X-Open-Key` 鉴权: - -`POST /api/open/token/rotate` - -- 按用户和 `scopeCode` 禁用当前有效 Token,创建新 Token。 -- 仅本次返回新 Token 明文;旧 Token 立即失效。 -- 记录旧 Token、新 Token 前缀、用户和 scope,禁止记录 Token 明文。 -- 具备幂等保护,避免并发换发产生多个有效 Token。 - -`POST /api/open/token/revoke` - -- 按用户和 `scopeCode` 禁用当前 Token。 -- 不返回 Token 明文;重复调用视为成功。 -- 用于退出登录后的主动失效、设备解绑和安全事件处置。 - -在 `rotate/revoke` 尚未实现前,退出登录只能清理客户端本地凭证,不能声称服务端 Token 已失效;管理员后台禁用仍是唯一服务端回收方式。 - ### 4.1b xiaoqu-ai 业务后端门面(待 xiaoqu-ai 侧实现) iOS 面向的接口(建议沿用原设计):POST `/api/v1/mcp/session-token`,`Authorization: Bearer `。业务后端从登录态解析用户 → 服务端查询手机号/邮箱 → 调 `ensure` → 返回 `{token, expires_at, user_id}`。不得使用请求体中的 `user_id/mobile/email` 覆盖登录态身份;客户端身份字段(client、device_id)由业务后端自行记录,agent-service 不感知。 @@ -231,12 +210,12 @@ Token 不写入该文件。客户端运行时读取当前用户 Token,并动 | 文件/目录 | 层级 | 类型 | 改动说明 | |---|---|---|---| | ~~`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/OpenTokenController.java` 等 | Open API | **ensure 已交付;get 待实现** | `ensure`、`get`、`rotate`、`revoke` 均使用 X-Open-Key 鉴权 | | `open/OpenKeyFilter.java` | Filter | **已交付** | 服务间静态密钥,常量时间比较,配置为空即关闭 | -| `admin/AdminTokenService.java` | Service | **已交付** | 一作用域一 Token;`issueSingle`/`findActiveToken` 供 ensure 复用 | +| `admin/AdminTokenService.java` | Service | **ensure 已交付;密文保存待实现** | 一作用域一 Token;签发时保存哈希和加密密文 | | `common/GlobalExceptionHandler.java` | Handler | **已交付** | 错误统一 `{"message": ...}` | | `security/AkAuthFilter.java` | Filter | 保持兼容 | `/mcp` Bearer ak 校验不变 | -| `src/test/...open` | Test | **已交付** | ensure 已存在/新建/404/停用作用域/缺参 5 个用例 | +| `src/test/...open` | Test | **部分已交付** | 增加 get/密文解密失败/密钥轮换/权限白名单测试 | ### xiaoqu-ai @@ -256,6 +235,7 @@ Token 不写入该文件。客户端运行时读取当前用户 Token,并动 |---|---|---:|---|---| | 1 | ~~服务端确认 JWT 与租户映射、Token exchange 契约~~ → xiaoqu-ai 业务后端实现门面接口(登录态 → ensure) | 2h | - | 门面 401/403 语义确定;明文按 user_id 加密落库 | | 2 | ~~服务端实现用户 MCP Token 签发与审计~~ **已交付**(ensure + 审计,2026-09-04) | - | - | 已上线并实测幂等/脱敏/错误语义 | +| 2b | 服务端增加 Token 加密保存与二次获取 | 3h | #2 | `get` 仅受信任应用可调用;加密密文可解密;明文不落日志 | | 3 | iOS Keychain 账号隔离和认证生命周期接入 | 3h | #1 | 登录/切换/退出无旧 Token 残留 | | 4 | Rust MCP HTTP Bearer、Session、初始化通知 | 3h | #3 | initialize/list/call 真实握手成功 | | 5 | MCP 工具到 Server 路由映射 | 2h | #4 | 多 Server 下工具调用不发错目标 | diff --git a/xiaoqu-agent-service/doc/xiaoqu-mcp-integration-setup.md b/xiaoqu-agent-service/doc/xiaoqu-mcp-integration-setup.md index eb80443..9cf2847 100644 --- a/xiaoqu-agent-service/doc/xiaoqu-mcp-integration-setup.md +++ b/xiaoqu-agent-service/doc/xiaoqu-mcp-integration-setup.md @@ -44,8 +44,9 @@ Content-Type: application/json - `X-Open-Key` 只能配置在受信任后端,不能下发 App。 - 业务后端必须从已验证用户会话查询手机号/邮箱,不能信任客户端或模型传入的身份字段。 -- `created=true` 才返回 Token 明文;业务后端必须立即加密保存。 -- `created=false` 不返回明文;Token 丢失前需先实现 rotate,退出登录需实现 revoke 才能立即失效。 +- 首次创建返回 Token 明文;xiaoqu-mcp 同时保存 SHA-256 哈希和 KMS/独立密钥加密密文。 +- 长期 Token 可由受信任业务后端通过 `POST /api/open/token/get` 二次获取;该接口不能下发 App 或暴露给模型。 +- Token 密文缺失或解密失败必须报错;不得回退到哈希值或公共 Token。 ## 4. 推荐实现 @@ -58,6 +59,7 @@ Content-Type: application/json | `XQ_MCP_ENABLED` | `false` | 是否启用 MCP capability | | `XQ_MCP_URL` | unset | 例如 `http://xiaoqu-mcp:8088`,只存地址,不存用户 Token | | `XQ_MCP_OPEN_KEY` | unset | 仅用于服务端 Token 初始化,使用 `SecretStr` | +| `XQ_MCP_TOKEN_GET_ENABLED` | `false` | 是否允许业务后端二次获取已有 Token | | `XQ_MCP_SCOPE_CODE` | `operation` | 固定作用域,禁止由 Planner 或客户端选择 | | `XQ_MCP_TIMEOUT_SECONDS` | `10` | MCP 请求超时 | @@ -73,7 +75,7 @@ Content-Type: application/json - `src/ops_assistant_service/mcp_token_provider.py` - 接收已验证的 `SharedIdentity`。 - 从受信任用户资料源取得 mobile/email。 - - 调用 `ensure`,首次创建时加密持久化明文。 + - 调用 `ensure`,首次创建时由 xiaoqu-mcp 加密保存;已有 Token 通过受控 `get` 获取。 - 不接受 `user_id/mobile/email` 作为客户端覆盖参数。 不引入通用 MCP 框架、第二个 Agent 或数据库直连;现有 `httpx` 已足够完成首期只读接入。 @@ -117,6 +119,7 @@ validate_tool_input - 登录用户变化:先关闭旧 Session,再建立新用户 Session。 - MCP 返回 401:清理当前用户 Token,最多执行一次重新获取和重连;不自动重放副作用请求。 - 退出登录:清理本地/服务端 Token 缓存;实现 revoke 后再主动撤销远端 Token。 +- 长期 Token 二次获取:仅由白名单业务后端调用 `token/get`,按用户和 scope 审计,不缓存响应。 - 日志只记录用户 ID、工具名、scope 和错误码,不记录 Token、Authorization Header 或手机号/邮箱明文。 - `tools/list` 仅用于校验远端能力;真正可调用集合以本地 allowlist 为准。