Files
beaver_project/authz-service/src

Authz-service

概览

这是一个基于 FastAPI 的认证授权服务,当前实现主要提供 4 类能力:

  1. 后端实例注册与管理
  2. 用户注册时自动绑定/创建后端
  3. 基于 OAuth 2.0 client_credentials 的 access token 签发
  4. token 内省、权限配置、Outlook 配置管理

文档内容按当前代码实现整理,接口定义来源于 app/main.py

鉴权模型

1. 当前真正支持的登录方式

当前服务没有“用户名 + 密码登录”接口。

真正用于获取 access token 的方式只有一种:

  • OAuth 2.0 client_credentials
  • 调用接口:POST /oauth/token
  • 需要提供:client_idclient_secretaud
  • 可选提供:scope

也就是说:

  • username / password 只在 POST /oauth/register 注册阶段出现
  • 其中 password 当前仅做“必填校验”,不会被保存,也不会参与后续登录或鉴权

2. 内部接口鉴权

以下接口要求请求头带内部 Bearer Token

  • GET /internal/backends/{backend_id}/settings/outlook
  • POST /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 调整

基础信息

  • 默认 issuerhttp://127.0.0.1:19090
  • 默认 token_endpoint/oauth/token
  • 默认 introspection_endpoint/oauth/introspect
  • 默认只声明支持 client_secret_post

如果服务已启动FastAPI 默认也会提供:

  • GET /docs
  • GET /openapi.json

典型流程

流程 A注册用户并自动创建后端

  1. 调用 POST /oauth/register
  2. 服务创建或更新 backend
  3. 服务返回 backend_idclient_id
  4. 如果 backend 是首次创建,还会返回一次性的 client_secret
  5. 客户端使用 client_id + client_secret + aud 调用 POST /oauth/token 换取 access token

流程 B单独注册 backend

  1. 调用 POST /backends/register
  2. 记录返回的 client_idclient_secret
  3. 配置 POST /backends/{backend_id}/permissions
  4. POST /oauth/token 获取 token

流程 C由 Auth Portal 发起的一站式注册

  1. Auth Portal 调用 POST /portal/register
  2. AuthZ 先调用 deploy-control 创建或解析实例
  3. AuthZ 再调用实例自己的 POST /api/auth/register
  4. 实例在注册过程中回调 AuthZ 的 /oauth/register / /backends/register
  5. 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
  • 用户数据当前只保存:usernameemaildefault_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 tokenPOST /oauth/token

这是当前服务唯一的“登录/鉴权入口”。

支持两种提交方式:

  • application/x-www-form-urlencoded
  • application/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
}

audscope 的校验规则

当前代码只支持两类 audience

1. MCP audience

格式:

mcp:<server_id>

例如:

mcp:outlook

只有当权限配置满足以下条件时才能签发 token

{
  "mcp": {
    "outlook": {
      "enabled": true,
      "tools": [
        "mail_list_messages",
        "mail_send_email"
      ]
    }
  }
}

这时允许的 scope 会自动变成:

  • list_tools
  • tool:mail_list_messages
  • tool: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 缺少 usernamepasswordbase_url,或 grant_type 不支持
401 client_id/client_secret 无效,或内部 Bearer Token 无效
403 backend 被禁用、audience 未启用、请求 scope 超出权限
404 backend 不存在,或内部 Outlook 配置不存在
409 注册 backend 时 backend_id 已存在

当前实现限制

  1. /oauth/registerpassword 目前不会被保存,也不会用于认证。
  2. 没有用户名密码登录接口,只有 client_credentials
  3. 没有 refresh token。
  4. 公开的 backend 管理接口当前未做访问控制,调用方需自行在网关或网络层保护。