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 的匹配流程,我画出来帮你理清思路:

ACL 访问控制匹配流程 ACL 访问控制匹配流程 客户端请求到达 匹配 client 规则? 是 应用 client 规则 否 匹配 user 规则? 是 应用 user 规则 否 应用全局 topic 规则 允许 / 拒绝 匹配到即停止,不再继续

这个流程其实很简单:客户端请求进来后,先看有没有匹配的 client 规则,有就直接用;没有就查 user 规则;再没有就用全局 topic 规则。匹配到任何一条就立即生效,不再往下走。

嗯,ACL 的内容就讲到这里。记住一句话:权限控制不是限制,而是保护。合理的 ACL 配置能让你的 MQTT 系统既安全又灵活。