客户端授权
客户端通过认证后,FlowMQ 对其发布、订阅、消费等每一次操作执行授权校验,仅放行策略允许的操作。
授权采用基于**策略(Policy)**的模型:每条策略声明哪些身份可以对哪些资源执行哪些动作,以及允许还是拒绝。授权配置与策略均按命名空间隔离,不同租户互不影响。
按命名空间启用
授权配置是按命名空间独立的,包含两个开关:
| 配置项 | 说明 | 默认值 |
|---|---|---|
| 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 按如下顺序裁决:
- 若该命名空间未启用授权(
enabled=false),直接放行。 - 在匹配该身份与资源的策略中,只要存在一条
DENY,立即拒绝(显式拒绝优先)。 - 若存在匹配的
ALLOW策略,则放行。 - 没有任何策略匹配时,按命名空间的
deny_if_no_match裁决:true拒绝,false放行。
跨协议资源语义
授权资源按协议独立:MQTT 使用 topic,Kafka 使用 stream,二者互不影响。跨协议互通在路由层通过主题过滤器绑定配置,不共享授权资源。
订阅相关的两条补充规则:
- 共享订阅按其底层主题过滤器授权,与普通订阅一致。
- 订阅请求的过滤器不得宽于策略允许的范围,否则订阅被拒绝。
通过 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<string> | 动作列表(READ、WRITE、ALL 等) |
resources | array | 资源列表,每项含 type、name、match |
principals_all | bool | 是否匹配所有身份,默认 true |
principal_ids | array<string> | 身份标识列表(支持通配符) |
principal_authenticators | array | 认证器列表,每项含 type、name |
principal_attributes | map<string, array<string>> | 属性约束 |
创建策略示例——只允许 team=sensors 的身份向 sensors/ 子树发布:
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 即上文“无匹配时拒绝”的开关):
PUT /v1/namespaces/default/authorization/config
Content-Type: application/json
{ "enabled": true, "deny_if_no_match": true }策略变更动态生效,无需重启。
Dashboard 操作
在 Dashboard 的 Authorization 页面可以:
- 为当前命名空间开启/关闭授权,并设置无匹配时的兜底决策(
ALLOW/DENY) - 创建、编辑、启用/停用和删除授权策略
- 按身份、资源、动作组合定义允许或拒绝规则
授权配置与策略均按命名空间隔离,切换命名空间即查看与管理对应租户的规则。