- 将所有环境变量前缀从NANO_改为BEAVER_ - 更新README.md文档内容,包括项目介绍、组件说明和快速开始指南 - 修改.gitignore文件,添加auth-portal运行时路径排除规则 - 更新app-instance镜像标签从nano/app-instance改为beaver/app-instance - 增强技能安全检查器,支持工具前缀白名单功能 - 添加技能草稿重新检查安全性API端点 - 扩展证据选择器,收集工具调用名称用于技能学习 - 改进技能合成器,基于实际调用的工具生成工具提示 - 优化路由超时处理机制,增加重试逻辑 - 更新后端架构文档,添加可视化入口和基础概念说明 - 实现在WebSocket消息中传递工具迭代次数信息
Authz-service
概览
这是一个基于 FastAPI 的认证授权服务,当前实现主要提供 4 类能力:
- 后端实例注册与管理
- 用户注册时自动绑定/创建后端
- 基于 OAuth 2.0
client_credentials的 access token 签发 - token 内省、权限配置、Outlook 配置管理
文档内容按当前代码实现整理,接口定义来源于 app/main.py。
鉴权模型
1. 当前真正支持的登录方式
当前服务没有“用户名 + 密码登录”接口。
真正用于获取 access token 的方式只有一种:
- OAuth 2.0
client_credentials - 调用接口:
POST /oauth/token - 需要提供:
client_id、client_secret、aud - 可选提供:
scope
也就是说:
username/password只在POST /oauth/register注册阶段出现- 其中
password当前仅做“必填校验”,不会被保存,也不会参与后续登录或鉴权
2. 内部接口鉴权
以下接口要求请求头带内部 Bearer Token:
GET /internal/backends/{backend_id}/settings/outlookPOST /oauth/introspect
请求头格式:
Authorization: Bearer <AUTHZ_INTERNAL_TOKEN>
服务会将 Bearer Token 与环境变量 AUTHZ_INTERNAL_TOKEN 做精确匹配。
3. Token 签名与校验
- 签名算法:
RS256 - JWKS 地址:
GET /.well-known/jwks.json - OAuth 元数据地址:
GET /.well-known/oauth-authorization-server - access token 默认有效期:
3600秒,可通过环境变量AUTHZ_ACCESS_TOKEN_TTL_SECONDS调整
基础信息
- 默认
issuer:http://127.0.0.1:19090 - 默认
token_endpoint:/oauth/token - 默认
introspection_endpoint:/oauth/introspect - 默认只声明支持
client_secret_post
如果服务已启动,FastAPI 默认也会提供:
GET /docsGET /openapi.json
典型流程
流程 A:注册用户并自动创建后端
- 调用
POST /oauth/register - 服务创建或更新 backend
- 服务返回
backend_id、client_id - 如果 backend 是首次创建,还会返回一次性的
client_secret - 客户端使用
client_id + client_secret + aud调用POST /oauth/token换取 access token
流程 B:单独注册 backend
- 调用
POST /backends/register - 记录返回的
client_id和client_secret - 配置
POST /backends/{backend_id}/permissions - 用
POST /oauth/token获取 token
流程 C:由 Auth Portal 发起的一站式注册
- Auth Portal 调用
POST /portal/register - AuthZ 先调用 deploy-control 创建或解析实例
- AuthZ 再调用实例自己的
POST /api/auth/register - 实例在注册过程中回调 AuthZ 的
/oauth/register//backends/register - AuthZ 将最终 token 和 backend 连接信息回传给 Auth Portal
注册时需要提供什么信息
用户注册接口:POST /oauth/register
请求体字段如下:
| 字段 | 是否必填 | 类型 | 说明 |
|---|---|---|---|
username |
是 | string |
用户名,去掉首尾空格后不能为空 |
password |
是 | string |
当前仅用于必填校验,不会被持久化,也不会用于后续登录 |
email |
否 | string |
用户邮箱 |
name |
否 | string |
backend 名称的候选值之一 |
backend_name |
否 | string |
backend 名称 |
backend_id |
否 | string |
指定 backend 唯一标识;不传则自动生成 |
base_url |
否 | string |
backend 服务地址 |
public_base_url |
否 | string |
base_url 的候选字段之一 |
frontend_base_url |
否 | string |
backend 前端地址 |
backend |
否 | object |
嵌套 backend 配置,优先级高于同名顶层字段 |
其中真正影响 backend 创建/更新的有效字段有:
- backend 名称:按以下优先级取值
backend.name->backend_name->name->username - backend id:按以下优先级取值
backend.backend_id->backend_id-> 自动生成 - backend 服务地址:按以下优先级取值
backend.base_url->public_base_url->base_url - backend 前端地址:按以下优先级取值
backend.frontend_base_url->frontend_base_url
base_url 最终必须能解析出有效值,否则会返回 400 base_url is required。
推荐最小请求体
{
"username": "alice",
"password": "any-non-empty-value",
"email": "alice@example.com",
"backend_name": "Alice Workspace",
"base_url": "https://api.example.com",
"frontend_base_url": "https://app.example.com"
}
使用嵌套 backend 的请求体
{
"username": "alice",
"password": "any-non-empty-value",
"email": "alice@example.com",
"backend": {
"name": "Alice Workspace",
"backend_id": "alice-workspace",
"base_url": "https://api.example.com",
"frontend_base_url": "https://app.example.com"
}
}
成功响应
{
"user": {
"username": "alice",
"email": "alice@example.com",
"default_backend_id": "alice-workspace",
"created_at": "2026-03-13T08:00:00+00:00",
"updated_at": "2026-03-13T08:00:00+00:00"
},
"backend": {
"backend_id": "alice-workspace",
"client_id": "alice-workspace",
"client_secret": "generated-secret-only-when-created",
"name": "Alice Workspace",
"base_url": "https://api.example.com",
"frontend_base_url": "https://app.example.com",
"status": "active",
"created_at": "2026-03-13T08:00:00+00:00"
}
}
特别说明
- 如果
backend_id不存在,服务会新建 backend,并返回新的client_secret - 如果
backend_id已存在,服务会更新该 backend 信息,但client_secret会返回null - 用户数据当前只保存:
username、email、default_backend_id - 当前实现没有用户密码校验、密码存储、密码登录、刷新 token
后端注册接口:POST /backends/register
请求体字段如下:
| 字段 | 是否必填 | 类型 | 说明 |
|---|---|---|---|
name |
是 | string |
backend 名称 |
base_url |
是 | string |
backend 服务地址 |
backend_id |
否 | string |
指定 backend id;不传则按 name 自动生成 |
frontend_base_url |
否 | string |
backend 前端地址 |
示例:
{
"name": "Local Backend",
"base_url": "https://api.example.com",
"backend_id": "local-backend",
"frontend_base_url": "https://app.example.com"
}
成功响应:
{
"backend_id": "local-backend",
"client_id": "local-backend",
"client_secret": "generated-secret",
"created_at": "2026-03-13T08:00:00+00:00",
"frontend_base_url": "https://app.example.com"
}
登录/鉴权时需要什么信息
换取 access token:POST /oauth/token
这是当前服务唯一的“登录/鉴权入口”。
支持两种提交方式:
application/x-www-form-urlencodedapplication/json
请求字段如下:
| 字段 | 是否必填 | 类型 | 说明 |
|---|---|---|---|
grant_type |
否 | string |
仅支持 client_credentials,默认值也是这个 |
client_id |
是 | string |
注册 backend 后得到,默认等于 backend_id |
client_secret |
是 | string |
注册或轮转密钥时得到 |
aud |
是 | string |
目标 audience,决定可申请哪些 scope |
scope / scopes |
否 | string / string[] |
需要的权限范围;不传则自动下发该 audience 下允许的全部 scope |
说明:
- 表单模式下使用字段
scope,多个 scope 用空格分隔 - JSON 模式下使用字段
scopes,类型为字符串数组 - JSON 模式下也可以把
aud换成resource吗:不可以。resource只在表单解析分支里作为aud的兜底字段
表单请求示例
curl -X POST http://127.0.0.1:19090/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=local-backend" \
-d "client_secret=<client-secret>" \
-d "aud=mcp:outlook" \
-d "scope=list_tools tool:mail_list_messages"
JSON 请求示例
{
"grant_type": "client_credentials",
"client_id": "local-backend",
"client_secret": "<client-secret>",
"aud": "mcp:outlook",
"scopes": [
"list_tools",
"tool:mail_list_messages"
]
}
成功响应
{
"access_token": "<jwt>",
"token_type": "bearer",
"expires_in": 3600
}
aud 和 scope 的校验规则
当前代码只支持两类 audience:
1. MCP audience
格式:
mcp:<server_id>
例如:
mcp:outlook
只有当权限配置满足以下条件时才能签发 token:
{
"mcp": {
"outlook": {
"enabled": true,
"tools": [
"mail_list_messages",
"mail_send_email"
]
}
}
}
这时允许的 scope 会自动变成:
list_toolstool:mail_list_messagestool:mail_send_email
2. A2A audience
格式:
a2a:<agent_id>
例如:
a2a:planner
只有当权限配置满足以下条件时才能签发 token:
{
"a2a": {
"enabled": true,
"agents": [
"planner"
]
}
}
这时允许的 scope 只有:
run_task
3. 请求 scope 的规则
- 如果不传
scope/scopes,服务会返回该 audience 下允许的全部 scope - 如果传了
scope/scopes,必须是允许 scope 的子集,否则返回403 Requested scopes exceed backend permissions - 如果 audience 未启用,返回
403 Audience is not enabled for this backend - 当前默认开启
AUTHZ_MCP_PERMISSIVE_DEFAULT=1- 对
mcp:*audience,会优先放行本次请求里声明的 scopes,并自动补list_tools - 如果你后面要改回严格模式,把这个环境变量设成
0
- 对
Token 内省:POST /oauth/introspect
此接口是内部接口,必须带内部 Bearer Token。
请求头:
Authorization: Bearer <AUTHZ_INTERNAL_TOKEN>
请求体:
{
"token": "<access-token>"
}
成功响应:
{
"active": true,
"client_id": "local-backend",
"backend_id": "local-backend",
"aud": "mcp:outlook",
"scp": [
"list_tools",
"tool:mail_list_messages"
],
"exp": 1773392400
}
token 无效时返回:
{
"active": false
}
接口清单
公开接口
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
GET |
/healthz |
无 | 健康检查 |
GET |
/.well-known/oauth-authorization-server |
无 | OAuth 元数据 |
GET |
/.well-known/jwks.json |
无 | JWKS 公钥 |
POST |
/backends/register |
无 | 注册 backend |
GET |
/backends |
无 | 查询全部 backend |
GET |
/backends/{backend_id} |
无 | 查询单个 backend |
PUT |
/backends/{backend_id} |
无 | 更新 backend |
POST |
/backends/{backend_id}/disable |
无 | 禁用 backend |
POST |
/backends/{backend_id}/enable |
无 | 启用 backend |
POST |
/backends/{backend_id}/rotate-secret |
无 | 轮转 client secret |
GET |
/backends/{backend_id}/permissions |
无 | 查询权限配置 |
POST |
/backends/{backend_id}/permissions |
无 | 保存权限配置 |
GET |
/backends/{backend_id}/settings/outlook |
无 | 查询 Outlook 配置,密码会被隐藏 |
POST |
/backends/{backend_id}/settings/outlook |
无 | 保存 Outlook 配置 |
DELETE |
/backends/{backend_id}/settings/outlook |
无 | 删除 Outlook 配置 |
POST |
/oauth/register |
无 | 用户注册并绑定 backend |
POST |
/oauth/token |
无 | 使用 client credentials 获取 token |
内部接口
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
GET |
/internal/backends/{backend_id}/settings/outlook |
Authorization: Bearer <AUTHZ_INTERNAL_TOKEN> |
查询完整 Outlook 配置,包含密码 |
POST |
/oauth/introspect |
Authorization: Bearer <AUTHZ_INTERNAL_TOKEN> |
内省 token |
详细接口说明
1. GET /healthz
响应:
{
"status": "ok"
}
2. GET /.well-known/oauth-authorization-server
响应示例:
{
"issuer": "http://127.0.0.1:19090",
"token_endpoint": "http://127.0.0.1:19090/oauth/token",
"introspection_endpoint": "http://127.0.0.1:19090/oauth/introspect",
"jwks_uri": "http://127.0.0.1:19090/.well-known/jwks.json",
"grant_types_supported": [
"client_credentials"
],
"token_endpoint_auth_methods_supported": [
"client_secret_post"
]
}
3. GET /.well-known/jwks.json
返回 RSA 公钥集合,用于校验 RS256 access token。
4. GET /backends
返回全部 backend 列表。
5. GET /backends/{backend_id}
- backend 不存在时返回
404 Backend not found
6. PUT /backends/{backend_id}
请求体字段:
| 字段 | 是否必填 | 类型 | 说明 |
|---|---|---|---|
name |
否 | string |
backend 名称 |
base_url |
否 | string |
backend 服务地址 |
frontend_base_url |
否 | string |
backend 前端地址 |
说明:
- 只有非空字段才会更新
- 当前实现不支持把已有字段显式清空为
null
7. POST /backends/{backend_id}/disable
将 backend 状态改为 disabled。
8. POST /backends/{backend_id}/enable
将 backend 状态改为 active。
9. POST /backends/{backend_id}/rotate-secret
返回新的 client_secret,并覆盖旧的凭证。
成功响应:
{
"backend_id": "local-backend",
"client_id": "local-backend",
"client_secret": "new-generated-secret",
"rotated_at": "2026-03-13T08:00:00+00:00"
}
10. GET /backends/{backend_id}/permissions
返回指定 backend 的权限配置;若从未配置,返回空对象 {}。
11. POST /backends/{backend_id}/permissions
请求体是任意 JSON 对象,当前实际生效的结构建议如下:
{
"mcp": {
"outlook": {
"enabled": true,
"tools": [
"mail_list_messages",
"mail_send_email"
]
}
},
"a2a": {
"enabled": true,
"agents": [
"planner"
]
}
}
服务会原样保存该 JSON。
12. GET /backends/{backend_id}/settings/outlook
返回 Outlook 配置,但不会暴露明文密码。
未配置时响应:
{
"configured": false
}
已配置时响应示例:
{
"configured": true,
"email": "user@example.com",
"username": "user",
"domain": "example.com",
"service_endpoint": null,
"server": "mail.example.com",
"autodiscover": false,
"default_timezone": "Asia/Shanghai",
"updated_at": "2026-03-13T08:00:00+00:00",
"password_masked": true
}
13. POST /backends/{backend_id}/settings/outlook
请求体字段:
| 字段 | 是否必填 | 类型 | 说明 |
|---|---|---|---|
configured |
否 | boolean |
默认 true,一般传 true |
email |
是 | string |
邮箱地址 |
username |
是 | string |
Outlook 用户名 |
domain |
否 | string |
域名 |
service_endpoint |
否 | string |
服务地址 |
server |
否 | string |
邮件服务器 |
autodiscover |
否 | boolean |
是否自动发现 |
default_timezone |
否 | string |
默认时区,默认 Asia/Shanghai |
password |
是,但更新时可空 | string |
初次配置必须传;后续更新可传空字符串表示沿用旧密码 |
初次配置时如果没有密码,会返回:
{
"detail": "Password is required for initial Outlook setup"
}
14. DELETE /backends/{backend_id}/settings/outlook
成功响应:
{
"ok": true
}
15. GET /internal/backends/{backend_id}/settings/outlook
内部接口,返回完整 Outlook 配置,包含明文 password。
16. POST /oauth/register
见上文“注册时需要提供什么信息”。
17. POST /oauth/token
见上文“登录/鉴权时需要什么信息”。
18. POST /oauth/introspect
见上文“Token 内省”。
常见错误码
| HTTP 状态码 | 场景 |
|---|---|
400 |
缺少 username、password、base_url,或 grant_type 不支持 |
401 |
client_id/client_secret 无效,或内部 Bearer Token 无效 |
403 |
backend 被禁用、audience 未启用、请求 scope 超出权限 |
404 |
backend 不存在,或内部 Outlook 配置不存在 |
409 |
注册 backend 时 backend_id 已存在 |
当前实现限制
/oauth/register的password目前不会被保存,也不会用于认证。- 没有用户名密码登录接口,只有
client_credentials。 - 没有 refresh token。
- 公开的 backend 管理接口当前未做访问控制,调用方需自行在网关或网络层保护。