> 内容来源：Onevium 官方文档
> 文章: 应用密钥、scope 与业务用户隔离
> 原文: https://onevium.com/zh/docs/developers/authentication
> 语言：简体中文
> 更新于: 2026-09-09
> 适用版本: 1.1.23+

---

# 应用密钥、scope 与业务用户隔离

对照真实 UI 和 Gateway 路由选择七种 scope，正确传递业务主体，并完成密钥更换、轮换和撤销。

## 应用密钥验证什么

业务后端用 **App Key** 调用 Gateway；桌面登录、设备连接、入站 Webhook 签名与 App Key 是不同身份通道。

每个业务请求同时包含：

```text
Authorization: Bearer <应用密钥>
X-Onevium-Subject: <由业务后端确认的用户主体>
```

密钥确定 owner、应用和可用 scope；`X-Onevium-Subject` 指定这个应用内的业务用户。Gateway 不会替你的系统验证该用户是否登录，也不会把请求正文里的 `owner_id`、`app_id` 当成授权。

## 创建和取得密钥

1. 打开 **渠道 → 外部接入**，创建应用或选中已有应用。首次创建应用生成的密钥只显示一次，默认包含全部七种 scope。
2. 为具体后端创建较小权限的凭据：在选中应用上点击**创建 API 密钥**。
3. 逐项勾选操作，点击**创建**；关闭凭据弹窗前保存到后端的凭证存储。
4. 在应用的**凭证与历史 → API 密钥**查看密钥前缀、Active/Revoked 状态，以及轮换/撤销入口。这里不会重新显示原密钥全文。

Bearer 填一次展示的完整应用密钥，不能用列表前缀或 `key_id` 代替；也不要填桌面登录令牌或私有控制令牌。

创建对话框的 **“对话接入”** 预设实际勾选全部七项；**“只读”** 勾选四个 `:read` 项。预设不是为你的业务计算的最小权限，应逐项核对。至少选择一项，未知或重复 scope 不被接受。

![只读 API 密钥权限示例：可以查询状态和历史，不能发送消息或启动任务。](https://onevium.com/docs/external-access/3cc390708/zh/15-key-permissions.png "只读 API 密钥权限示例：可以查询状态和历史，不能发送消息或启动任务。")

图中为预览界面的只读权限演示，使用隔离示例数据；不表示已创建真实密钥或通过真实业务授权验收。

## 七种真实 scope

| UI 名称     | API 枚举                | 允许的操作                            |
| --------- | --------------------- | -------------------------------- |
| 查看已发布服务   | `endpoints:read`      | 列出当前应用的已发布服务                     |
| 读取会话历史    | `conversations:read`  | 列会话、读快照/历史、会话 SSE，以及查询 operation |
| 发送消息和重开会话 | `conversations:write` | 新建会话、发送消息、reset                  |
| 查看任务状态    | `runs:read`           | 查询 run 和 run SSE                 |
| 提交和停止任务   | `runs:write`          | 从服务直接创建 run、取消 run               |
| 下载附件      | `resources:read`      | 下载当前主体可访问的资源内容                   |
| 上传附件      | `resources:write`     | 预留资源并上传内容                        |

没有 `*`、`admin` 或 `webhooks:write` 这类 App Key scope。`conversations:write` 包含 reset，且 reset 可以使用 `cancel_and_reset` 停止本代工作；它不是“只能发文字、不能停止任务”的权限。

Scope 只控制 API 操作类别，不会增加服务已经发布的工具、目录、Skills 或 MCP 能力。请求方不能通过添加 `model`、`tools`、`working_directory` 等未知字段来扩权。

## 每类公开接口需要什么权限

以下路径均属于 Gateway 的业务 API。除 `/health` 和单独签名的 Webhook 外，表内接口还要求 App Key 与非空 subject。

| 方法和路径                                       | 必需 scope              | 说明                                                                               |
| ------------------------------------------- | --------------------- | -------------------------------------------------------------------------------- |
| `GET /api/v1/agent-endpoints`               | `endpoints:read`      | 当前应用的已发布服务列表                                                                     |
| `POST /api/v1/agent-endpoints/{id}/runs`    | `runs:write`          | 任务型入口                                                                            |
| `POST /api/v1/conversations`                | `conversations:write` | 创建业务会话                                                                           |
| `GET /api/v1/conversations?endpoint_id=...` | `conversations:read`  | endpoint\_id 用于定位设备                                                              |
| `GET /api/v1/conversations/{id}`            | `conversations:read`  | 快照                                                                               |
| `GET /api/v1/conversations/{id}/messages`   | `conversations:read`  | 分页历史                                                                             |
| `POST /api/v1/conversations/{id}/messages`  | `conversations:write` | 发送消息                                                                             |
| `GET /api/v1/conversations/{id}/events`     | `conversations:read`  | 会话 SSE                                                                           |
| `POST /api/v1/conversations/{id}/reset`     | `conversations:write` | 重开上下文                                                                            |
| `GET /api/v1/runs/{id}`                     | `runs:read`           | 状态和结果                                                                            |
| `GET /api/v1/runs/{id}/events`              | `runs:read`           | 单次任务 SSE                                                                         |
| `POST /api/v1/runs/{id}/cancel`             | `runs:write`          | 停止任务，请求体 `{}`                                                                    |
| `GET /api/v1/operations/{id}`               | `conversations:read`  | 即使 operation 来自 run cancel，也使用这个 scope                                           |
| `POST /api/v1/resources`                    | `resources:write`     | 声明 endpoint\_id、content\_type、size                                               |
| `PUT /api/v1/resources/{id}/content`        | `resources:write`     | 上传字节；还需匹配类型、长度和 `X-Content-SHA256`                                               |
| `GET /api/v1/resources/{id}`                | `resources:read`      | 返回文件内容，不是资源 JSON 元数据                                                             |
| `GET /health`                               | 不使用 App Key           | Gateway 健康信息，不证明设备或模型执行成功                                                        |
| `POST /api/v1/webhooks/{integration_id}`    | 使用 Webhook 签名         | 见[Webhook 教程](https://onevium.com/zh/docs/developers/webhooks)，不能只用 App Key 代替签名 |

创建会话、发送消息、直接创建 run、cancel、reset 和预留资源都需要各自操作的 `Idempotency-Key`。资源内容 PUT 使用内容哈希与不可变资源约束，不应把它当成创建新资源的重试。

## 应用和 subject 如何隔离

同一 owner 下不同应用的服务、会话、任务和资源仍分开。读取已有资源时，会同时核对应用和 subject；换成另一个 subject 读取原用户资源，通常得到 **404**，不是返回别人的内容。

但 App Key **没有绑定某个 subject**：持有密钥的后端有责任填入正确主体。不要直接透传浏览器提交的 `X-Onevium-Subject`、URL 用户 ID 或表单值。建议从已验证登录态构造稳定内部标识，并在多租户系统中包含租户维度。

下面只是业务后端伪代码，不是 Onevium SDK：

```typescript
const user = requireAuthenticatedUser(request); // 你现有的登录校验
const subject = `tenant:${user.tenantId}:user:${user.id}`;
const gatewayHeaders = {
  Authorization: `Bearer ${serverSecrets.oneviumAppKey}`,
  "X-Onevium-Subject": subject
};
```

subject 必须非空，最多 256 字符且无首尾空白。显示名称和邮箱容易变化，不适合作为持久会话关联。读取、SSE、附件下载和控制操作都要沿用相同主体。

同一应用的不同 API 密钥只在 scope 上有区别，不自动隔离用户或服务。若两个业务系统连密钥持有方都不能互相访问，应使用不同应用，而不是只给同一应用多建两把密钥。

## 按真实功能选择最小范围

| 后端用途             | 起始 scope 集合                                            |
| ---------------- | ------------------------------------------------------ |
| 仅查询已知 run 的结果    | `runs:read`                                            |
| 直接提交独立任务并查结果     | `runs:write`、`runs:read`                               |
| 创建/续聊，查看历史和结果    | `conversations:write`、`conversations:read`、`runs:read` |
| 在上述会话流程中单独取消 run | 再增加 `runs:write`                                       |
| 上传或下载附件          | 分别增加 `resources:write`、`resources:read`                |
| 需要先让用户选择已发布服务    | 再增加 `endpoints:read`                                   |

请求 `operations/{id}` 需要 `conversations:read`。仅配 `runs:write + runs:read` 可以发起 cancel 并查 run，却不能查询控制操作完成状态；需要这个完整闭环时补上 `conversations:read`。

## 用真实身份验证一次

先从凭证存储加载 `APP_KEY`，把 Gateway 地址和业务主体设置为测试值。下面只读取当前应用的服务，需要 `endpoints:read`：

**这里的 `GATEWAY_URL` 只填网关 origin，不包含末尾 `/api/v1`。** 例如界面显示 `http://127.0.0.1:48541/api/v1` 时，变量填 `http://127.0.0.1:48541`；下方命令会自行追加 `/api/v1`，不要重复。

```bash
: "${APP_KEY:?Load APP_KEY from your credential store first}"
export GATEWAY_URL='http://127.0.0.1:REPLACE_WITH_API_PORT'
export SUBJECT='docs-test-user'
curl --fail-with-body -sS "$GATEWAY_URL/api/v1/agent-endpoints" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT"
```

成功应返回 200 和 `endpoints` 数组。空数组表示这个应用没有可见的已发布服务，不代表需要扩大到管理员权限。

建议用另建的只读测试密钥尝试一项写操作，确认收到 403；再按[会话教程](https://onevium.com/zh/docs/developers/conversations)用两个测试 subject 检查隔离。这些是你需实际执行的验收，不是本页已完成的测试。

**权限不足的示例响应（非实测）：**

```json
{
  "error": {
    "code": "FORBIDDEN",
    "message": "Application scope is not allowed."
  }
}
```

## 更换、轮换和撤销密钥

在**凭证与历史 → API 密钥**找到正确前缀，选择**轮换密钥 → 确认轮换**，保存新显示的密钥。轮换保留原 scope，并撤销旧密钥；**没有给旧密钥保留宽限期**。

需要平滑切换时使用两把密钥：

1. **创建 API 密钥**，选择新后端需要的 scope。
2. 将新密钥部署到业务后端，使用原 subject 验证所需读写流程。
3. 确认所有调用方已切换，再对旧前缀选择**撤销密钥 → 确认撤销**。
4. 检查旧密钥的新请求被拒绝。若需要减少 scope，也应新建密钥；轮换不会自动缩小旧权限。

撤销影响后续鉴权请求，不等同于取消正在执行的任务。已有 SSE 连接也不是每条事件都重新做密钥鉴权；客户端更换密钥后应主动断开、用新密钥重连。单个任务的停止使用明确的 cancel，完整停用应用是影响整个应用的另一项管理操作。

## 业务凭据不能管理 Gateway

当前 V1 业务入口供后端访问：带浏览器 `Origin`，或跨站 `Sec-Fetch-Site` 的请求会被拒绝。应由已鉴权的业务后端调用，不把 App Key 发给浏览器。

密钥不放 URL；`token`、`key`、`ticket` 这类查询参数会被拒绝。Onevium 页面使用的 `/api/integrations/cloud/...` 是桌面管理桥接地址，不是这里的公开 Gateway API。Node Gateway 的公开 `/api/v1/management/**` 返回 404，不能给 App Key 增加 scope 来打开它。

远端管理使用独立私有控制通道和令牌；设备连接使用设备身份；Webhook 使用签名。不要混用这几类凭据，远端配置见[远端网关](https://onevium.com/zh/docs/developers/remote-gateway)。

## 错误与验收重点

| 现象                    | 先检查什么                             |
| --------------------- | --------------------------------- |
| 401 `UNAUTHENTICATED` | Bearer 格式、正确环境的密钥、是否轮换/撤销、应用是否仍有效 |
| 403 `FORBIDDEN`       | 所需 scope、owner/设备状态，以及是否错误地从浏览器直连 |
| 400 `INVALID_REQUEST` | subject 缺失/无效、URL 放了凭据、请求字段不符合接口  |
| 已知 ID 返回 404          | 应用、subject、设备路由和实际 ID 是否匹配        |
| 轮换后仍有旧流输出             | 关闭旧 SSE，使用新密钥重连；不要把流输出当成新鉴权成功     |

真实环境还应检查：最小 scope、跨应用/subject 隔离、密钥轮换后的调用方切换、旧密钥拒绝、设备离线及恢复，以及需要的 HTTPS/SSE/回调路径。

## 下一步

回到[发布与首次请求](https://onevium.com/zh/docs/developers/external-access)，实现[业务会话](https://onevium.com/zh/docs/developers/conversations)，或配置[Webhook 和结果回调](https://onevium.com/zh/docs/developers/webhooks)。
