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