---
title: 客户端授权
---

# 客户端授权

客户端通过[认证](./authentication)后，FlowMQ 对其发布、订阅、消费等每一次操作执行授权校验，仅放行策略允许的操作。

授权采用基于**策略（Policy）**的模型：每条策略声明哪些身份可以对哪些资源执行哪些动作，以及允许还是拒绝。授权配置与策略均按[命名空间](./authentication#多租户与命名空间隔离)隔离，不同租户互不影响。

## 按命名空间启用

授权配置是**按命名空间独立**的，包含两个开关：

| 配置项 | 说明 | 默认值 |
|--------|------|--------|
| **enabled** | 是否对该命名空间启用授权校验。关闭时放行所有操作 | 关闭 |
| **deny_if_no_match** | 没有任何策略匹配时是否拒绝：`true` 拒绝（安全默认），`false` 放行 | `true` |

授权默认关闭，确保开箱即用；管理员可随时为某个命名空间开启，配置动态生效并持久化保存，无需重启。

::: tip
`deny_if_no_match` 在认证关闭（匿名接入）的协议上尤为重要：若 `enabled=true` 且 `deny_if_no_match=true`，在没有匹配策略时所有操作都会被拒绝。此时应将 `deny_if_no_match` 设为 `false`，或显式编写允许匿名身份的策略。
:::

## 策略模型

一条策略包含以下要素：

| 字段 | 说明 |
|------|------|
| **effect** | 决策效果：`ALLOW`（允许）或 `DENY`（拒绝） |
| **actions** | 适用的动作列表（见下表） |
| **resources** | 适用的资源列表（类型 + 名称 + 匹配方式） |
| **principals** | 适用的身份：可按用户 ID、认证器、属性等条件选择，或匹配全部 |
| **enabled** | 该策略是否生效 |

### 资源类型

资源类型按各协议的用户心智划分：

| 资源类型 | 对应协议 | 含义 |
|----------|----------|------|
| **topic** | MQTT / NATS | MQTT 主题 / NATS subject |
| **stream** | Kafka | Kafka 主题 |
| **queue** | AMQP | AMQP 队列 |
| **exchange** | AMQP | AMQP 交换机 |
| **consumer-group** | Kafka | Kafka 消费组 |

::: info
目前授权校验在 MQTT、Kafka、NATS 的发布/订阅/消费路径上强制执行；`queue`、`exchange`、`consumer-group` 等资源类型已纳入模型，对应的策略执行将逐步完善。
:::

### 动作

| 动作 | 说明 |
|------|------|
| **ALL** | 匹配所有动作 |
| **READ** | 读取（订阅 / 消费） |
| **WRITE** | 写入（发布 / 生产） |
| **CREATE** | 创建资源 |
| **DELETE** | 删除资源 |
| **DESCRIBE** | 查询元数据 |
| **ALTER** | 修改配置 |
| **LIST** | 列举 |

各协议操作到动作的映射示例：

| 协议操作 | 资源类型 | 动作 |
|----------|----------|------|
| MQTT 发布 | topic | WRITE |
| MQTT 订阅 | topic | READ |
| Kafka 生产（Produce） | stream | WRITE |
| Kafka 消费（Fetch） | stream | READ |
| Kafka 创建主题 | stream | CREATE |
| Kafka 删除主题 | stream | DELETE |

### 资源匹配方式

策略资源的 `match` 字段决定资源名的匹配方式，取值为 `filter`（默认）或 `literal`。订阅类操作的请求资源名可能是带通配符的过滤器（如 MQTT `SUBSCRIBE foo/#`），发布/生产类操作的请求资源名是具体主题，`filter` 模式对两者的处理不同。

#### filter（过滤器，默认）

将资源名作为主题过滤器解释，`+`、`#` 按 MQTT 通配规则展开。匹配条件按 `effect` 区分：

| effect | 匹配条件 | 含义 |
|--------|----------|------|
| ALLOW | 策略过滤器**覆盖**请求过滤器 | 请求可能命中的每个主题都在策略允许范围内 |
| DENY | 策略过滤器与请求过滤器**相交** | 存在至少一个被两者同时命中的主题 |

对具体主题（发布/生产）的请求，“覆盖”与“相交”等价，即过滤器是否匹配该主题。示例：

- `ALLOW filter foo/+` 不放行 `SUBSCRIBE foo/#`：`foo/#` 可命中 `foo/a/b`，超出 `foo/+` 的范围。
- `DENY filter secret/#` 拒绝 `SUBSCRIBE #`：两者相交。

#### literal（精确）

将资源名作为普通字符串精确匹配，`+`、`#` 视为字面字符而非通配符。`literal foo/#` 仅匹配资源名恰好为 `foo/#` 的请求，不匹配 `foo/+` 或 `foo/bar`。

literal 用于精确禁止某个过宽的订阅。例如，放行对 `foo/` 子树的订阅，同时禁止一次性订阅整棵子树 `foo/#`：

```
ALLOW   filter    foo/#
DENY    literal   foo/#
```

此时客户端可订阅 `foo/+`、`foo/bar` 等，而 `SUBSCRIBE foo/#` 被 DENY 精确拦截（[显式拒绝优先](#决策流程)）。

### 动态占位符

资源名中可使用占位符，在裁决时按当次连接的身份与连接信息展开，从而用一条策略覆盖“每个用户只能访问以自己命名的主题”这类规则：

| 占位符 | 含义 |
|--------|------|
| `${principal.id}` | 身份标识 |
| `${principal.authenticator}` | 通过认证的认证器实例名 |
| `${principal.authenticatorType}` | 认证器类型（如 `password`、`webhook`） |
| `${principal.attributes.<名称>}` | 身份属性中的指定项（如 Webhook 下发的属性） |
| `${connection.clientId}` | 协议层客户端标识 |
| `${connection.sourceIP}` | 客户端 IP |
| `${connection.protocol}` | 协议名 |

例如 `ALLOW WRITE topic filter user/${principal.id}/#`，让每个身份只能向以自身 ID 为前缀的主题发布。取不到的占位符展开为空串。

### 身份选择（principals）

`principals` 决定一条策略对哪些身份生效，由以下条件组合：

| 字段 | 说明 |
|------|------|
| **all** | 为 `true` 时匹配所有身份，忽略其余条件 |
| **ids** | 身份标识列表，支持通配符（glob），命中任一即可 |
| **authenticators** | 认证器列表（类型 + 名称），须精确命中其一；用于“仅对某认证器认证的身份生效” |
| **attributes** | 属性约束：`属性名 → 允许值列表`。身份须具备该属性，且取值命中列表之一 |

匹配规则：

- `all=true` 时直接匹配全部身份。
- 否则，每个**非空**条件都是一道必须通过的约束（条件之间为“且”）；某个条件留空表示不在该维度设限。
- `attributes` 中多个属性之间为“且”，同一属性的多个允许值之间为“或”；允许值列表为空表示只要求该属性存在。
- 若 `all=false` 且 `ids`、`authenticators`、`attributes` 全部为空，则不匹配任何身份。

## 决策流程

对每一次操作，FlowMQ 按如下顺序裁决：

1. 若该命名空间未启用授权（`enabled=false`），直接放行。
2. 在匹配该身份与资源的策略中，只要存在一条 `DENY`，立即拒绝（**显式拒绝优先**）。
3. 若存在匹配的 `ALLOW` 策略，则放行。
4. 没有任何策略匹配时，按命名空间的 `deny_if_no_match` 裁决：`true` 拒绝，`false` 放行。

## 跨协议资源语义

授权资源按协议独立：MQTT 使用 `topic`，Kafka 使用 `stream`，二者互不影响。跨协议互通在路由层通过[主题过滤器绑定](./streaming#主题过滤器绑定)配置，不共享授权资源。

订阅相关的两条补充规则：

- **共享订阅**按其底层主题过滤器授权，与普通订阅一致。
- 订阅请求的过滤器不得宽于策略允许的范围，否则订阅被拒绝。

## 通过 API 管理

策略与授权配置可通过管理 API 维护，基础路由为 `/v1/namespaces/{ns}/authorization`（`{ns}` 为命名空间，默认 `default`）。

| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/policies` | 分页列出策略（`cursor`、`limit`） |
| `GET` | `/policies/{name}` | 查看单条策略 |
| `POST` | `/policies` | 创建策略 |
| `PUT` | `/policies/{name}` | 更新策略 |
| `DELETE` | `/policies/{name}` | 删除策略 |
| `GET` | `/config` | 查看授权配置（`enabled`、`deny_if_no_match`） |
| `PUT` | `/config` | 设置授权配置 |

策略的 JSON 字段：

| 字段 | 类型 | 说明 |
|------|------|------|
| `name` | string | 策略名（唯一标识，1–256 字符） |
| `description` | string | 备注 |
| `effect` | string | `ALLOW` 或 `DENY`，默认 `ALLOW` |
| `enabled` | bool | 是否生效，默认 `true` |
| `actions` | array&lt;string&gt; | 动作列表（`READ`、`WRITE`、`ALL` 等） |
| `resources` | array | 资源列表，每项含 `type`、`name`、`match` |
| `principals_all` | bool | 是否匹配所有身份，默认 `true` |
| `principal_ids` | array&lt;string&gt; | 身份标识列表（支持通配符） |
| `principal_authenticators` | array | 认证器列表，每项含 `type`、`name` |
| `principal_attributes` | map&lt;string, array&lt;string&gt;&gt; | 属性约束 |

创建策略示例——只允许 `team=sensors` 的身份向 `sensors/` 子树发布：

```http
POST /v1/namespaces/default/authorization/policies
Content-Type: application/json

{
  "name": "sensors-can-publish",
  "effect": "ALLOW",
  "actions": ["WRITE"],
  "resources": [
    { "type": "topic", "name": "sensors/#", "match": "filter" }
  ],
  "principals_all": false,
  "principal_attributes": { "team": ["sensors"] }
}
```

设置授权配置（`deny_if_no_match` 即上文“无匹配时拒绝”的开关）：

```http
PUT /v1/namespaces/default/authorization/config
Content-Type: application/json

{ "enabled": true, "deny_if_no_match": true }
```

策略变更动态生效，无需重启。

## Dashboard 操作

在 Dashboard 的 **Authorization** 页面可以：

- 为当前命名空间开启/关闭授权，并设置无匹配时的兜底决策（`ALLOW` / `DENY`）
- 创建、编辑、启用/停用和删除授权策略
- 按身份、资源、动作组合定义允许或拒绝规则

授权配置与策略均按命名空间隔离，切换命名空间即查看与管理对应租户的规则。
