4. ACL 访问控制列表:给 MQTT 加上门禁系统
ACL,全称 Access Control List,翻译过来就是访问控制列表。说白了,它就是 MQTT 服务器的门禁系统。没有它,任何客户端都能订阅任何主题、发布任何消息——这在生产环境里简直是灾难。
我记得刚入行那会儿,有个项目上线第二天就出事了。某个测试客户端误发了控制指令,导致生产线停了半小时。后来一查,根本没人做权限控制。从那以后,ACL 就成了我每个项目的标配。
核心观点:ACL 是 MQTT 安全的第一道防线。它决定了「谁」能对「哪个主题」做「什么操作」。
4.1 ACL 文件语法与规则
大部分 MQTT Broker(比如 Mosquitto)都支持基于文件的 ACL 配置。这个文件通常叫 acl.conf 或 mosquitto.acl,语法非常简洁。
先看一个最基本的例子:
# 允许所有人订阅 /public/#
topic read /public/#
# 只允许 admin 用户发布到 /control/#
user admin
topic write /control/#
# 拒绝所有其他操作
topic deny #
嗯,这里要注意几个关键点:
- 顺序很重要:ACL 规则是从上往下匹配的。匹配到第一条就停止,不再继续往下查。所以「允许」规则要放在「拒绝」规则前面。
- 通配符:
+匹配单层,#匹配多层。跟 MQTT 主题通配符完全一致。 - 权限类型:
read表示订阅,write表示发布,readwrite表示两者都允许。
我的习惯:我会在 ACL 文件末尾加一条 topic deny # 作为兜底规则。这样即使前面漏写了什么,也不会意外放行。
ACL 文件支持三种匹配模式:
| 模式 | 语法 | 说明 |
|---|---|---|
| 基于用户 | user <用户名> |
只对指定用户生效 |
| 基于客户端 ID | client <clientid> |
只对指定客户端 ID 生效 |
| 全局模式 | topic <权限> <主题> |
对所有匿名或未匹配的用户生效 |
4.2 基于主题的读写权限控制
这是 ACL 最常用的场景。你想想看,一个物联网系统里,传感器只应该上报数据,控制器只应该接收指令,管理后台才需要读写所有主题。
我做过一个智慧农业项目,设备分类很清晰:
# 温度传感器:只能发布数据
user temp_sensor_001
topic write /sensor/temperature/+
# 湿度传感器:只能发布数据
user humidity_sensor_001
topic write /sensor/humidity/+
# 灌溉控制器:只能订阅指令
user irrigator_001
topic read /control/irrigator/+
# 管理后台:可以读写所有
user admin
topic readwrite /#
为什么会这样设计?因为传感器如果也能订阅,就可能被恶意利用来监听其他设备的数据。权限最小化原则在这里体现得淋漓尽致。
我曾经踩过的坑:有个项目里,我把传感器的权限设成了 readwrite,想着「反正它也不会主动订阅」。结果某次固件升级后,传感器开始订阅控制主题,导致控制指令被大量回显,网络直接打爆。从那以后,我坚持「只给最小必要权限」。
主题权限控制还有一个进阶用法——动态主题。比如每个设备只能操作自己的主题:
# 设备只能发布到自己的数据主题
user device_001
topic write /device/001/data
topic read /device/001/config
user device_002
topic write /device/002/data
topic read /device/002/config
这种模式在设备数量少的时候还行。但如果设备有上千台,手动写 ACL 文件就不现实了。这时候需要用到基于客户端 ID 的权限控制,或者干脆上动态 ACL。
4.3 基于客户端的权限控制
基于客户端 ID 的 ACL,说白了就是不看「你是谁」,而是看「你用什么名字连上来」。这在某些场景下比基于用户更灵活。
语法很简单:
# 只允许特定客户端 ID 订阅告警主题
client alarm_monitor_01
topic read /alarm/#
# 允许固件升级客户端发布升级包
client ota_server
topic write /ota/upgrade/#
我个人习惯把客户端 ID 的 ACL 放在用户 ACL 之前。因为客户端 ID 更底层,匹配优先级更高。这样即使某个用户有权限,如果客户端 ID 被限制,照样无法操作。
来看一个完整的对比:
| 控制维度 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 基于用户 | 逻辑清晰,易于管理 | 需要配合认证系统 | 多用户共享设备 |
| 基于客户端 ID | 粒度更细,无需用户体系 | 客户端 ID 可能被伪造 | 嵌入式设备、传感器 |
| 混合使用 | 双重保障,安全性最高 | 配置复杂,维护成本高 | 高安全要求的工业场景 |
你可能会问:客户端 ID 不是很容易伪造吗?确实。所以基于客户端 ID 的 ACL 通常要配合 TLS 客户端证书一起用。证书里可以嵌入客户端 ID,这样伪造的难度就大多了。
我的建议:如果设备数量少于 100 台,用静态 ACL 文件就够了。超过这个量级,建议上数据库或 Redis 做动态 ACL。Mosquitto 1.6 之后支持了 auth_plugin 接口,可以自己写插件实现动态权限控制。
4.4 ACL 配置实战:一个完整的例子
下面是我在一个智能楼宇项目里用过的 ACL 配置,你可以参考:
# ========== 全局默认规则 ==========
# 允许任何人读取公共状态
topic read /building/status/+
# ========== 传感器设备 ==========
# 温度传感器:只写
client temp_sensor_%
topic write /building/sensor/temperature/+
# 门禁传感器:只写
client door_sensor_%
topic write /building/sensor/door/+
# ========== 执行器设备 ==========
# 灯光控制器:只读
client light_controller_%
topic read /building/control/light/+
# 空调控制器:只读
client hvac_controller_%
topic read /building/control/hvac/+
# ========== 管理用户 ==========
user admin
topic readwrite /building/#
user operator
topic read /building/status/#
topic read /building/sensor/#
topic write /building/control/light/#
# ========== 兜底拒绝 ==========
topic deny #
这个配置里有个小技巧:客户端 ID 用了 % 作为通配符。Mosquitto 的 ACL 支持 %c 和 %u 等占位符,可以在运行时替换成实际的客户端 ID 或用户名。不过要注意,这个功能需要开启 acl_require_prefix 选项。
重要提醒:ACL 文件修改后,大部分 Broker 需要重启才能生效。Mosquitto 可以通过发送 SIGHUP 信号来热加载:kill -SIGHUP <pid>。生产环境里我建议先测试再 reload,别直接重启。
4.5 ACL 核心逻辑图
下面这张图展示了 ACL 的匹配流程,我画出来帮你理清思路:
这个流程其实很简单:客户端请求进来后,先看有没有匹配的 client 规则,有就直接用;没有就查 user 规则;再没有就用全局 topic 规则。匹配到任何一条就立即生效,不再往下走。
嗯,ACL 的内容就讲到这里。记住一句话:权限控制不是限制,而是保护。合理的 ACL 配置能让你的 MQTT 系统既安全又灵活。