Files
beaver_project/app-instance/backend-old/鉴权.md

1243 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 鉴权方案设计
本文用于明确当前 `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<br/>JSON storage]
BE[Backend<br/>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>`
- `backend_id`
- `aud`: `mcp:outlook``a2a:<agent_id>`
- `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