# 鉴权方案设计 本文用于明确当前 `nanobot-backend` 后续要落地的鉴权和配置边界,重点覆盖: 1. 一个前端管理多个 backend 的注册与身份模型 2. backend 调 A2A / MCP 时的统一鉴权方式 3. Outlook 外置 MCP 的权限校验与凭据读取方式 4. 当前仓库与目标方案之间的差距 5. 第一阶段可落地的 JSON 版 OAuth `AuthZ Service` 设计 本文按 2026-03-11 的需求收敛,不采用“一个 backend 多个 sandbox”的模型。当前结论是: - 一个 backend 就是一个独立的 agent runtime,也是一个独立的安全主体 - 一个前端可以管理多个 backend - 当前先按一对一模拟实现,但数据模型和注册流程要为多个 backend 预留 ## 1. 结论先行 最终要落成的不是“模型拿着 Outlook 账号密码调用工具”,而是下面这条链路: 1. 用户在前端管理界面配置 Outlook 账号密码 2. 前端把配置保存到独立的 `AuthZ Service` 3. backend 自己只持有 `backend_id` 和自己的 OAuth client 身份 4. backend 调 Outlook MCP 时先向 `AuthZ Service` 的 token endpoint 申请 access token,再带 token 调用,不带账号密码 5. Outlook MCP 校验 token 和权限 6. Outlook MCP 再去 `AuthZ Service` 读取该 backend 的 Outlook 配置 7. Outlook MCP 用取回的配置去执行真实 Outlook 操作 8. 模型只能看到工具和结果,不能看到账号密码 这套方案的关键点有三个: - `backend_id` 是主体标识,不是凭证 - 真正的鉴权依赖 `AuthZ Service` 作为 OAuth Authorization Server 签发的 access token - Outlook 凭据和 backend access token 是两套不同数据,不能混用 ## 2. 设计目标 ### 2.1 必须满足 1. backend 必须先完成注册,注册后才能获得自己的身份 2. A2A 和 MCP 都要能识别“当前是谁在调我” 3. `list_tools` 和 `call_tool` 都必须鉴权 4. Outlook 账号密码不进入模型上下文,不进入 prompt,不作为工具参数传给模型 5. 前端需要能查看当前配置状态,但默认只能看到脱敏后的敏感字段 6. 当前阶段先允许用 JSON 做 `AuthZ Service` 存储 ### 2.2 当前阶段不做 1. 一个 backend 下再切多个 sandbox 2. 复杂多租户组织、团队、成员模型 3. 完整的密钥托管系统 4. OAuth/OIDC 全套企业级接入 ## 3. 主体与信任边界 ### 3.1 主体定义 本方案只有一个核心安全主体: - `backend` 换句话说: - 一个 `backend` = 一个独立 agent runtime - 一个 `backend` = 一个独立权限实体 - 一个 `backend` = 一份独立的 Outlook 配置归属 ### 3.2 角色划分 #### Frontend 负责: - 管理 backend 注册 - 管理 backend 的 Outlook 配置 - 查询 backend 状态、权限状态、配置状态 不负责: - 直接决定工具是否可调用 - 自己保存 backend 的长期信任根 #### Backend 负责: - 跑 agent - 调 A2A / MCP - 持有自己的注册身份 - 调用前向 `AuthZ Service` 申请短期 token 不负责: - 保存 Outlook 密码 - 本地决定 Outlook 权限是否放行 #### AuthZ Service 负责: - backend 注册中心 - backend 凭证校验 - 权限配置 - settings 存储 - 作为 OAuth Authorization Server 签发 access token - 提供 OAuth metadata / JWKS / introspection - 给 MCP/A2A 做 token 校验或 introspection #### External Outlook MCP 负责: - 校验 backend 身份 - 判断 backend 是否开通 Outlook MCP 和具体工具权限 - 从 `AuthZ Service` 获取 Outlook 配置 - 以服务端身份完成 Outlook 调用 ### 3.3 总体架构图 ```mermaid flowchart LR UI[Frontend] AZ[AuthZ Service
JSON storage] BE[Backend
Agent Runtime] A2A[A2A Remote Agent] MCP[External Outlook MCP] O365[Outlook / Exchange] UI -->|register backend| AZ UI -->|save masked settings| AZ UI -->|view backend status| AZ UI -->|chat / manage| BE BE -->|request short-lived token| AZ BE -->|A2A request + Bearer token| A2A BE -->|list_tools / call_tool + Bearer token| MCP A2A -->|verify or introspect token| AZ MCP -->|verify or introspect token| AZ MCP -->|load backend outlook settings| AZ MCP -->|execute mail/calendar action| O365 ``` ### 3.4 OAuth 角色映射 为避免后续扩多个 backend / 多个受保护 MCP / A2A 时重新设计,建议现在就按标准 OAuth 角色建模: 1. `AuthZ Service` - OAuth Authorization Server - 同时也是业务配置源 2. `backend` - OAuth client - 当前阶段是 confidential client 3. `Outlook MCP` - Resource Server 4. `受保护的 A2A agent` - Resource Server 这样做的好处是: 1. MCP 和 A2A 共用同一套 access token 模型 2. 后续可以平滑增加更多 backend 3. 后续可以平滑增加更多受保护资源服务 4. 公开第三方服务仍可保留 `auth_mode=none` ### 3.5 推荐服务结构 如果单独做一个 `authz-service`,建议结构至少拆成下面这样: ```text authz-service/ ├── app/ │ ├── main.py │ ├── api/ │ │ ├── backends.py │ │ ├── permissions.py │ │ ├── settings.py │ │ └── oauth.py │ ├── core/ │ │ ├── auth.py │ │ ├── oauth_tokens.py │ │ ├── scopes.py │ │ └── settings_access.py │ ├── storage/ │ │ ├── json_store.py │ │ ├── backends_repo.py │ │ ├── credentials_repo.py │ │ ├── permissions_repo.py │ │ └── settings_repo.py │ └── models/ │ ├── backend.py │ ├── oauth.py │ ├── permission.py │ └── settings.py └── data/ ├── backends.json ├── backend_credentials.json ├── permissions.json └── settings.json ``` 对应职责: 1. `api/backends.py` - backend 注册、查询、禁用、启用 2. `api/oauth.py` - `/.well-known/oauth-authorization-server` - `/.well-known/jwks.json` - `/oauth/token` - `/oauth/introspect` 3. `core/oauth_tokens.py` - 生成 JWT access token - 校验 scope / audience 4. `storage/*.py` - 对 JSON 文件做原子读写 ### 3.6 backend 侧配置结构 backend 侧建议只保留身份与路由配置,不保留 Outlook 业务凭据。 推荐最小结构: ```json { "backend_identity": { "backend_id": "backend_local_001", "client_id": "backend_local_001", "client_secret": "generated-secret" }, "authz": { "base_url": "http://127.0.0.1:19090" }, "a2a_targets": { "planner": { "base_url": "https://planner.example.com", "auth_mode": "oauth_backend_token" }, "public-search-agent": { "base_url": "https://public.example.com", "auth_mode": "none" } }, "mcp_targets": { "outlook": { "url": "https://mcp.example.com/outlook", "auth_mode": "oauth_backend_token" }, "public-fetcher": { "url": "https://mcp.example.com/public", "auth_mode": "none" } } } ``` 这样做的边界是: 1. backend 知道“去哪里申请 token” 2. backend 知道“某个目标该不该带 OAuth token” 3. backend 不知道 Outlook 账号密码 ## 4. 当前仓库现状与缺口 当前仓库已经有 A2A、MCP、Outlook 集成,但鉴权边界还不满足目标方案。 ### 4.1 A2A 现状 当前 A2A 客户端会从 `agent.auth_env` 指定的环境变量读取 token,再塞进 `Authorization` 请求头。 相关代码: - `nanobot/a2a/client.py` 当前问题: 1. token 是静态环境变量,不是为当前 backend 动态签发 2. token 与 backend 注册体系没有绑定 3. 无法表达更细的 audience 和 scope ### 4.2 MCP 现状 当前 MCP 连接方式支持: - `stdio` - HTTP `url + headers` 相关代码: - `nanobot/config/schema.py` - `nanobot/agent/tools/mcp.py` 当前问题: 1. MCP 连接建立后,工具会被直接注册到本地 registry 2. `list_tools()` 阶段没有按 backend 身份做鉴权 3. `call_tool()` 阶段没有按 backend 身份做细粒度校验 4. HTTP 模式也只是静态 `headers`,不是按每次调用动态签 token ### 4.3 Outlook 现状 当前 Web 侧 Outlook 集成会把配置写到 workspace 对应的外部状态文件里,并自动把 Outlook MCP 注册为本地 MCP server。 相关代码: - `nanobot/web/outlook.py` - `nanobot/web/server.py` 当前问题: 1. Outlook 账号密码当前仍然是 workspace 级保存 2. Outlook 配置与 backend 注册体系还没有打通 3. Outlook MCP 的注册方式仍偏向“backend 本地接入工具”,而不是“外置服务按 backend 鉴权” ### 4.4 Web 接口现状 当前 Web 登录接口存在,但大部分管理接口没有统一接入鉴权依赖。 相关代码: - `nanobot/web/server.py` 当前问题: 1. 登录后只是在内存里保存一个 Web bearer token 2. 大部分管理路由没有显式要求该 token 3. 这套 Web 登录还不是 backend 注册体系的一部分 ## 5. 目标模型 ### 5.1 backend 作为唯一安全主体 本方案不再引入额外的 `sandbox_id` 概念,而是直接使用: - `backend_id` 它同时承担: - 权限主体标识 - Outlook 配置归属标识 - A2A / MCP 调用身份的业务主键 ### 5.2 backend 标识与凭证分离 必须区分这三类字段: 1. `backend_id` - 稳定主键 - 可被前端展示 - 可用于日志 2. `client_id` - backend 向 `AuthZ Service` 认证时使用 - 可以等于 `backend_id` 3. `client_secret` - backend 的长期凭证 - 只在注册成功返回时展示一次 - `AuthZ Service` 只保存 hash ### 5.3 token 作为实际调用凭证 backend 在调用 A2A / MCP 前,不直接拿 `client_secret` 调目标服务,而是先向 `AuthZ Service` 的 OAuth token endpoint 申请短期 access token。 该 token 至少包含: - `sub`: `backend:` - `backend_id` - `aud`: `mcp:outlook` 或 `a2a:` - `scp`: scope 列表 - `iat` - `exp` - `jti` 推荐: - 默认过期时间 5 分钟到 15 分钟 - 面向单个目标服务签发 - 面向单次或短时窗口调用复用 ### 5.4 OAuth 模型 当前阶段推荐直接按 OAuth 做,而不是再造一套与 OAuth 相似但不兼容的 token 系统。 建议第一版使用: 1. grant type - `client_credentials` 2. client 类型 - `confidential client` 3. token 类型 - `Bearer access token` 4. token 格式 - 优先 JWT - 必要时辅以 introspection 原因: 1. backend 到 MCP / A2A 是标准机器到机器调用 2. 多 backend 扩展时不需要换模型 3. 多 resource server 扩展时不需要换模型 4. 后续若要接入标准 SDK、网关或审计系统更容易 ## 6. 数据模型 当前阶段使用 JSON 做持久化,建议至少拆成 4 份文件,避免把身份、权限、配置和密钥混在一起。 ### 6.1 `backends.json` 保存 backend 主记录。 ```json { "backends": [ { "backend_id": "backend_local_001", "name": "Local Backend", "base_url": "http://127.0.0.1:18080", "status": "active", "created_at": "2026-03-11T10:00:00Z", "updated_at": "2026-03-11T10:00:00Z" } ] } ``` ### 6.2 `backend_credentials.json` 保存 backend 长期凭证的 hash,不存明文。 ```json { "credentials": [ { "backend_id": "backend_local_001", "client_id": "backend_local_001", "client_secret_hash": "hashed-secret", "created_at": "2026-03-11T10:00:00Z", "rotated_at": null } ] } ``` ### 6.3 `permissions.json` 保存 backend 对 A2A / MCP 的能力授权。 ```json { "permissions": { "backend_local_001": { "mcp": { "outlook": { "enabled": true, "tools": ["list_mail", "read_mail", "send_mail"] } }, "a2a": { "enabled": true, "agents": ["planner", "calendar-agent"] } } } } ``` ### 6.4 `settings.json` 保存业务配置。当前阶段可以临时把 Outlook 配置放在这里,但要明确这是过渡态。 ```json { "settings": { "backend_local_001": { "outlook": { "configured": true, "email": "user@corp.com", "username": "user", "domain": "corp", "service_endpoint": "https://mail.example.com/EWS/Exchange.asmx", "password": "plain-text-for-now", "updated_at": "2026-03-11T10:00:00Z" } } } } ``` 补充约束: 1. 前端查询配置时,默认不能返回明文密码 2. 管理页面只回显非敏感字段和“是否已配置” 3. 后续应迁移为: - `settings.json` 保存非敏感配置 - `secrets.json` 或外部 secret store 保存敏感值 ### 6.5 JSON 存储约束 既然当前阶段用 JSON 做存储,就必须明确写入约束,否则很容易因为并发更新把文件写坏。 建议最少满足: 1. 所有写操作先写临时文件,再原子替换 2. 同一类文件写入时加进程内锁 3. 文件格式损坏时要能快速回滚或人工修复 4. `backends.json`、`permissions.json`、`settings.json`、`backend_credentials.json` 不要混写 5. 每次写入都刷新 `updated_at` ## 7. 注册流程 backend 注册不是创建聊天账号,而是把一个 backend 纳入信任体系。 ### 7.1 注册步骤 1. 用户在前端注册页提交账号信息 2. 前端把用户信息发给 `AuthZ Service` 3. `AuthZ Service` 在注册流程中为当前 backend 生成: - `backend_id` - `client_id` - `client_secret` 4. `AuthZ Service` 同时创建一条 OAuth client 记录,并记录用户信息与 backend 归属 5. 前端把这组 backend 身份信息交给对应 backend 保存 6. backend 后续通过 `client_id + client_secret` 向 `AuthZ Service` 的 `/oauth/token` 申请 access token 7. 用户后续不再进入独立的 backend 列表页做这件事 ### 7.2 注册时序图 ```mermaid sequenceDiagram participant UI as Frontend participant AZ as AuthZ Service participant BE as Backend UI->>AZ: POST /oauth/register AZ-->>UI: user + backend_id + client_id + client_secret UI->>AZ: POST /backends/{id}/permissions AZ-->>UI: ok UI->>BE: 保存 backend identity ``` ### 7.3 注册后 backend 本地至少需要持有 1. `backend_id` 2. `client_id` 3. `client_secret` 4. `authz_base_url` 当前阶段建议: - backend 把这组配置写到本地配置文件或环境变量 - 前端不再反复下发明文 secret ### 7.4 backend 凭证轮换与禁用 虽然第一阶段可以不先做完整 UI,但模型必须预留以下动作: 1. 轮换 `client_secret` 2. 禁用 backend 3. 重新启用 backend 最少行为应定义为: 1. backend 被禁用后,`/oauth/token` 不再签发新 token 2. 已签发 token 到期后自然失效 3. backend 被重新启用后才恢复签发 ## 8. Outlook 配置流程 ### 8.1 目标 用户在前端界面录入 Outlook 配置后: 1. 配置进入 `AuthZ Service` 2. backend 本地不保存账号密码 3. Outlook MCP 需要时再向 `AuthZ Service` 读取 ### 8.2 流程图 ```mermaid sequenceDiagram participant UI as Frontend participant AZ as AuthZ Service UI->>AZ: POST /backends/{id}/settings/outlook Note over UI,AZ: email / username / password / endpoint AZ-->>UI: saved UI->>AZ: GET /backends/{id}/settings/outlook AZ-->>UI: masked config ``` ### 8.3 前端回显规则 MCP 详情页可以展示: - `email` - `username` - `domain` - `service_endpoint` - `configured` - `updated_at` MCP 详情页默认不直接展示: - 明文 `password` 推荐返回格式: ```json { "configured": true, "email": "user@corp.com", "username": "user", "domain": "corp", "service_endpoint": "https://mail.example.com/EWS/Exchange.asmx", "password_masked": true, "updated_at": "2026-03-11T10:00:00Z" } ``` ## 9. A2A 鉴权方案 ### 9.1 目标 A2A 侧要做到: 1. backend 身份可识别 2. 远端 agent 可以判断调用方是谁 3. 远端 agent 可以按 backend 控制是否允许访问 ### 9.2 公开第三方 A2A 兼容策略 不是所有第三方 A2A 都必须接入你们自己的 OAuth。 建议对 A2A 目标增加 `auth_mode`: 1. `none` - 公开第三方 agent - backend 可直接调用 2. `oauth_backend_token` - 你们自己的受保护 A2A agent - backend 先向 `AuthZ Service` 申请 access token 再调用 3. `static_secret` - 某些第三方需要固定 API key 或固定 bearer token 平台侧即便 `auth_mode=none`,也仍建议保留: 1. host allowlist 2. enable/disable 开关 3. 超时和并发限制 4. 审计日志 ### 9.3 当前方案与未来方案对比 当前: - backend 通过静态环境变量向 A2A 附带 Bearer Token 目标: 1. backend 在每次调用 A2A 前,向 `AuthZ Service` 请求短期 token 2. token 的 `aud` 绑定具体 A2A 目标 3. 远端 A2A 服务校验 token 或调用 introspection 4. 未授权时返回明确 `401/403` ### 9.4 推荐 token claim ```json { "sub": "backend:backend_local_001", "backend_id": "backend_local_001", "aud": "a2a:planner", "scp": ["run_task"], "iat": 1773209700, "exp": 1773210000, "jti": "uuid" } ``` ### 9.5 A2A agent card 暴露策略 建议分两层: 1. 公共 card - 只暴露最小信息 - 不承诺所有内部能力都可见 2. 鉴权后的能力视图 - 由服务端根据 backend 权限决定是否允许调用 实现上可以简化为: - card 可公开 - 真正调用时严格校验 `run_task` 权限 ## 10. MCP 鉴权方案 ### 10.1 结论 涉及 backend 身份鉴权的外置 MCP,优先统一走 HTTP transport,不再依赖本地 `stdio` 进程边界。 原因: 1. `stdio` 更像本机受信任进程通信 2. backend 身份、token、远端服务审计更适合 HTTP 3. `list_tools` 和 `call_tool` 都要做按 backend 的动态判断 ### 10.2 MCP 的认证模式 不是所有第三方 MCP 都必须接入你们的 OAuth。 建议 MCP 目标也增加 `auth_mode`: 1. `none` - 完全公开的第三方 MCP - 不要求 backend token 2. `oauth_backend_token` - 你们自己的受保护 MCP - backend 必须先拿 access token 再调用 3. `static_secret` - 某些第三方 HTTP MCP 需要固定 token 或 API key 其中: 1. Outlook MCP 应归类为 `oauth_backend_token` 2. 公开第三方 MCP 不应被这个方案破坏 3. 平台侧仍建议保留 host allowlist 和 enable/disable ### 10.3 `list_tools` 规则 `list_tools` 必须鉴权,不能再视为“只是列功能,不敏感”。 建议语义: 1. 未认证:`401` 2. backend 未开通该 MCP:`403` 3. backend 开通 MCP 但没有任何工具:返回空数组 4. backend 已开通且有部分工具权限:只返回允许的工具 ### 10.4 `call_tool` 规则 建议语义: 1. 未认证:`401` 2. token audience 错误:`403` 3. backend 未开通该工具:`403` 4. backend 已开通但 Outlook 未配置:`400` 5. 上游 Outlook 调用失败:按业务错误返回 ### 10.5 Outlook MCP 调用时序图 ```mermaid sequenceDiagram participant BE as Backend participant AZ as AuthZ Service participant MCP as Outlook MCP participant O365 as Outlook / Exchange BE->>AZ: POST /oauth/token (aud=mcp:outlook) AZ-->>BE: access_token BE->>MCP: list_tools / call_tool + Bearer token MCP->>AZ: introspect token AZ-->>MCP: backend_id + scopes valid MCP->>AZ: GET /internal/backends/{id}/settings/outlook AZ-->>MCP: email / username / password / endpoint MCP->>O365: execute actual action O365-->>MCP: result MCP-->>BE: tool result ``` ### 10.6 Outlook MCP 需要做的事 1. 从 token 中识别 `backend_id` 2. 查询该 backend 是否启用 `outlook` MCP 3. 查询该 backend 是否允许当前 `tool_name` 4. 查询该 backend 是否已配置 Outlook 5. 从 `AuthZ Service` 取回配置 6. 执行工具 7. 返回结果,不回传密钥 ### 10.7 MCP 工具缓存与失效 由于当前仓库会把已连接 MCP 的工具注册进本地 registry,因此即便未来一个 backend 只代表一个主体,也要定义缓存失效规则。 建议: 1. backend 启动后可缓存自己有权访问的工具列表 2. 当 `permissions.outlook.tools` 变更时,要求 backend 主动 reload MCP 3. 当 Outlook 配置从“已配置”变为“未配置”时,`call_tool` 不能依赖旧缓存继续执行 4. `list_tools` 的最终结果以 MCP 服务端鉴权结果为准,backend 本地缓存只作为性能优化 ## 11. AuthZ Service API 草案 当前阶段最小接口集如下。 ### 11.0 OAuth 基础端点 建议第一阶段就预留标准 OAuth 元数据端点: 1. `GET /.well-known/oauth-authorization-server` 2. `GET /.well-known/jwks.json` 3. `POST /oauth/token` 4. `POST /oauth/introspect` 这样后续 MCP / A2A resource server 不需要绑定你们的私有业务接口格式。 ### 11.1 backend 注册 `POST /backends/register` 请求: ```json { "name": "Local Backend", "base_url": "http://127.0.0.1:18080" } ``` 响应: ```json { "backend_id": "backend_local_001", "client_id": "backend_local_001", "client_secret": "generated-secret", "created_at": "2026-03-11T10:00:00Z" } ``` ### 11.2 查询 backend `GET /backends/{backend_id}` 响应: ```json { "backend_id": "backend_local_001", "name": "Local Backend", "base_url": "http://127.0.0.1:18080", "status": "active" } ``` ### 11.3 更新权限 `POST /backends/{backend_id}/permissions` 请求: ```json { "mcp": { "outlook": { "enabled": true, "tools": ["list_mail", "read_mail", "send_mail"] } }, "a2a": { "enabled": true, "agents": ["planner", "calendar-agent"] } } ``` ### 11.4 查询权限 `GET /backends/{backend_id}/permissions` ### 11.5 保存 Outlook 配置 `POST /backends/{backend_id}/settings/outlook` 请求: ```json { "email": "user@corp.com", "username": "user", "domain": "corp", "service_endpoint": "https://mail.example.com/EWS/Exchange.asmx", "password": "plain-text-for-now" } ``` ### 11.6 读取 Outlook 配置 对前端: - `GET /backends/{backend_id}/settings/outlook` 默认返回脱敏字段。 对 MCP 内部: - `GET /internal/backends/{backend_id}/settings/outlook` 只允许受信任服务访问,可返回完整配置。 ### 11.7 OAuth token endpoint `POST /oauth/token` 请求: ```json { "grant_type": "client_credentials", "client_id": "backend_local_001", "client_secret": "generated-secret", "aud": "mcp:outlook", "scopes": ["list_tools", "tool:read_mail"] } ``` 响应: ```json { "access_token": "jwt-or-signed-token", "token_type": "bearer", "expires_in": 300 } ``` 说明: 1. 当前阶段可以允许 `aud` 作为自定义字段 2. 后续若采用更标准实现,可改成 `resource` 或约定好的 scope 模型 ### 11.8 OAuth introspection endpoint `POST /oauth/introspect` 请求: ```json { "token": "jwt-or-signed-token" } ``` 响应: ```json { "active": true, "backend_id": "backend_local_001", "aud": "mcp:outlook", "scp": ["list_tools", "tool:read_mail"], "exp": 1773210000 } ``` ### 11.9 建议追加但可后做的接口 以下接口不是第一天必须做完,但建议作为后台/运维接口预留,不直接暴露成用户侧 backend 列表页: 1. `POST /backends/{backend_id}/rotate-secret` 2. `POST /backends/{backend_id}/disable` 3. `POST /backends/{backend_id}/enable` 4. `GET /backends` 5. `GET /audit/logs` 6. `POST /oauth/register` ## 12. 前端页面要求 当前阶段前端至少需要 3 个页面,不再给用户单独暴露 backend 列表页。 ### 12.1 登录页 展示与交互: - 用户名 / 密码登录 - 登录成功后进入主界面或 MCP 管理页 ### 12.2 注册页 展示与交互: - 用户名 - 邮箱 - 密码 - 注册成功后自动触发 `AuthZ Service` 里的用户信息记录与 backend/sandbox 通行证初始化 - 注册成功后把 backend identity 保存到当前 backend ### 12.3 MCP 管理页 / MCP 详情页 展示与编辑: - `email` - `username` - `domain` - `service_endpoint` - `password` - `configured` - `updated_at` 交互要求: 1. 敏感信息在对应 MCP 的详情页里保存 2. 保存请求直接发到 `AuthZ Service` 3. 保存后刷新状态 4. 默认展示脱敏后的配置状态 5. 重新编辑时允许覆盖旧密码 ## 13. 与当前仓库的改造边界 本文先定方案,不直接改代码,但需要明确后续改造点。 ### 13.1 backend 不再本地保存 Outlook 密码 当前: - `nanobot/web/outlook.py` 会把 Outlook 配置保存到 workspace 对应文件 目标: - `nanobot/web/outlook.py` 只负责调用 `AuthZ Service` - backend 本地只保留“是否已配置”的只读状态缓存,必要时甚至不缓存 ### 13.2 Outlook MCP 改为外置 HTTP MCP 当前: - Outlook MCP 更偏向“本地注册 MCP server” 目标: - Outlook MCP 是独立外置服务 - backend 调它时带 token - 它自己去 `AuthZ Service` 拉 Outlook 配置 ### 13.3 A2A 统一接入 backend identity 当前: - A2A 用静态 env token 目标: - A2A 与 MCP 统一使用 backend 短期 token ### 13.4 Web 管理接口需要统一鉴权 当前: - Web 登录存在 - 但管理路由没有统一接入鉴权依赖 目标: - 前端所有管理行为先经过 Web 登录 - backend 的注册与管理接口再与 `AuthZ Service` 对接 ## 14. 第一阶段落地顺序 建议按下面顺序推进,避免一次改散。 ### 阶段 1:做 `AuthZ Service` 先完成: 1. JSON 存储层 2. backend 注册接口 3. 权限接口 4. Outlook settings 接口 5. OAuth metadata / JWKS / token / introspect 接口 ### 阶段 2:做前端管理页 先完成: 1. backend 注册 2. Outlook 配置 3. Outlook 配置状态查看 ### 阶段 3:改 backend 先完成: 1. backend 接入自己的 `backend_id/client_secret` 2. 调 Outlook MCP 时自动申请 token 3. Outlook Web 配置接口改为转发到 `AuthZ Service` ### 阶段 4:改 Outlook MCP 先完成: 1. token 校验 2. 权限校验 3. 从 `AuthZ Service` 拉 Outlook 配置 4. `list_tools` / `call_tool` 分别按 backend 鉴权 ## 15. 风险与补缺 这部分是本方案里容易漏掉、但必须提前写清楚的点。 ### 15.1 明文密码只允许作为阶段性过渡 当前阶段为了快速模拟,可以先把 Outlook 密码明文存在 JSON 中,但必须明确: 1. 这不是最终方案 2. 文档和代码里都要标记为过渡态 3. 后续至少要改成加密存储或外部 secret store 如果业务坚持“前端管理界面必须能看见明文密码”,建议额外加一道控制: 1. 只有高权限操作者才能 reveal 2. reveal 前要求重新确认身份 3. 每次 reveal 写审计日志 ### 15.2 token 不能只带 `backend_id` 如果 token 只是一个可猜的 `backend_id`,那不是鉴权。 至少要满足: 1. 可校验签名或可 introspection 2. 有过期时间 3. 有 audience 4. 有 scope ### 15.3 `list_tools` 也属于敏感接口 不要把 `list_tools` 当作无害操作。 原因: 1. 工具名本身可能暴露系统能力 2. 工具参数 schema 可能透露内部实现 3. 有些工具枚举本身就是权限信息 ### 15.4 backend 与前端身份不能混用 前端登录态和 backend 调用态不是一回事。 必须区分: 1. 前端用户登录 token 2. backend 调 A2A / MCP 的 backend token ### 15.5 审计日志建议第一阶段就留口子 建议 `AuthZ Service` 和 Outlook MCP 至少记录: 1. `backend_id` 2. 调用目标 3. `tool_name` 4. 结果状态 5. 失败原因 6. 时间戳 但不要把密码、token 明文写入日志。 ### 15.6 配置删除与失效要有一致性 当 Outlook 配置被移除时,建议同时做到: 1. `settings.outlook.configured = false` 2. `permissions.mcp.outlook.enabled` 可选择自动关闭或显式保留 3. 后续 `call_tool` 必须返回“未配置” ### 15.7 MCP 与 AuthZ 的内部信任也要单独设计 不要让 Outlook MCP 匿名读取 `AuthZ Service` 的内部配置接口。 至少要满足: 1. Outlook MCP 自己也有一套服务端凭证 2. `GET /internal/backends/{id}/settings/outlook` 只允许受信任服务调用 3. backend 自己不能直接拿 backend token 访问 internal 明文配置接口 ## 16. 参考实现建议 当前阶段建议保守实现,不追求复杂化。 ### 16.1 `AuthZ Service` 技术选型 建议: - FastAPI - JSON 文件存储 - 进程内文件锁或原子写 ### 16.2 token 实现方式 二选一都可: 1. JWT 2. 自定义签名 token + introspection 当前阶段更省事的方式是: - 先做带签名的短期 JWT - MCP / A2A 无法本地验签时再走 introspection ### 16.3 Outlook MCP 访问 `AuthZ Service` 推荐: - Outlook MCP 使用内部服务凭证访问 `AuthZ Service` 的 internal API - 不直接复用 backend token 读内部明文配置 ## 17. 一句话边界总结 整个方案最终要保证的边界就是: - 前端负责配置 - `AuthZ Service` 负责存储和签发身份 - backend 负责拿身份去调服务 - Outlook MCP 负责验证身份并读取配置 - 模型只负责调用工具,不接触账号密码 ## 18. 外部规范参考 以下规范只作为设计参考,具体实现仍以本仓库实际边界为准: 1. A2A Specification - https://google-a2a.github.io/A2A/specification/ 2. A2A Enterprise-Ready Topics - https://google-a2a.github.io/A2A/topics/enterprise-ready/ 3. Model Context Protocol Authorization - https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization 4. Model Context Protocol OAuth Client Credentials Extension - https://modelcontextprotocol.io/extensions/auth/oauth-client-credentials