ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MQTT安全机制全解析:TLS双向认证、身份认证与ACL配置实战

MQTT安全机制全解析:TLS双向认证、身份认证与ACL配置实战

MQTT 现在基本成了物联网和工业场景里的“默认数据总线”,从智能车场的车牌识别终端上报,到工厂里 PLC 通过 S7.NET 网关往中台推送设备状态,再到 WebSocket 桥接的浏览器实时看板,到处都能看到它的身影。但很多人对 MQTT 的安全机制理解还停留在“1883 端口开起来就能用”的阶段——用户名密码是明文传输的,数据包抓一下就能看到内容,Topic 的访问控制全靠自觉。这篇文章我会把 MQTT 安全机制的原理拆开讲透,从传输加密、身份认证、访问控制到 MQTT 5.0 的新特性,再配上一套可以直接照着做的完整配置方案和避坑记录。

1. MQTT 的安全基因:轻量协议设计里藏着的先天短板

1.1 发布订阅模型给安全带来的天然优势

理解 MQTT 安全机制之前,得先明白这个协议的基因。MQTT 本质上是一个发布/订阅模式的轻量级消息协议,设计初衷是给低带宽、高延迟、设备资源受限的场景用的。它把消息的发送方和接收方完全解耦,设备只和 Broker 打交道,设备之间不直接建立连接。

这种架构在安全上有一个天然的好处:你不需要在每一对设备之间做点对点的安全协商。客户端只要把自己的身份证明交给 Broker,由 Broker 做统一的身份校验和权限判断,然后把消息路由到被授权的订阅者手里。这就好比所有数据先集中到一个物业中心,由物业中心验证访客身份再决定放不放行,而不是每户人家各自开门迎客。对于车牌识别这种终端设备数量多、分布散的场景,集中式鉴权的运维成本要低得多。

但集中式架构也意味着 Broker 成了整个信任链的“单点”,一旦 Broker 被攻破或者 Broker 的安全配置不合格,所有设备的数据都会暴露。这个特性决定了我们在做 MQTT 安全时,核心就是把 Broker 打造成一道高度可信的“安检闸机”。

1.2 协议层面的明文基因与信任模型的缺失

MQTT 协议本身确实没有内建加密能力。CONNECT 报文里虽然有用户名和密码字段,但这些字段在标准 MQTT 协议里就是普通字符串,不加任何保护。你用 Wireshark 在网卡上抓包,客户端发往 Broker 的 CONNECT 报文一展开,用户名和密码直接以明文形式躺在里面。

而且 MQTT 的客户端身份标识就靠一个 ClientID 字符串。Broker 默认只认这个 ID 是否唯一,不认这个 ID 背后是不是真的那台设备——只要知道设备 ID,任何客户端都能伪装成它去连接 Broker。这跟 HTTP 协议只靠浏览器里的 Cookie 认人是一个道理,没有底层加密和双向验证兜底,伪造太容易了。

还有一个经常被忽略的问题:MQTT 的遗嘱消息和保留消息默认情况下是允许任意客户端随意发布的。这意味着攻击者如果成功连接上了 Broker,它甚至可以伪造遗嘱消息把整个状态上报链路搅乱。协议本身不对这些消息做任何权限判断,权限判断完全要靠上层配置去补。

1.3 和 HTTP、TCP 这些常见协议放在一起看安全定位

很多人会拿 MQTT 和 HTTP 做比较。HTTPS 之所以安全,本质是在 HTTP 外面套了一层 TLS。MQTT 的安全路径是完全一样的思路,在 TCP 之上叠加 TLS,端口从 1883 变成 8883,数据就开始被加密了。

不过 MQTT 的安全模型和 HTTPS 有一个重要的区别。浏览器访问 HTTPS 网站时,通常只需要验证服务器端的证书是否可信,客户端本身是不需要出示证书的。但 MQTT 的物联网场景里,Broker 面对的是海量的无人值守设备,Broker 只验证服务器身份是不够的——它更需要确认“连接进来的这台设备是不是我们发给车场那个摄像头终端”或者“是不是工厂 3 号线的那个采集网关”。所以 MQTT 安全实践里通常建议做双向 TLS 认证,让 Broker 和客户端互相验证身份。

至于 TCP、UDP、串口这类更底层的通信,它们压根没有安全机制的概念。TCP 的 SYN 洪水攻击、UDP 的源地址伪造、串口线的物理窃听,这些风险 MQTT 管不到。MQTT 的安全设计是在它自己的协议边界内做文章,底层传输安全仍然要靠网络隔离、防火墙和物理防护去补。理解了这个层次关系,后面看各种安全配置就不会觉得乱。

2. 分层的安全防线:TLS 加密、身份认证和 Topic 访问控制

2.1 传输层加密:为什么强烈建议直接上双向 TLS

MQTT 传输层安全的根基是 TLS。TLS 在这里承担三件事:数据机密性让抓包的人看到的是一堆密文、数据完整性防止消息在链路中被篡改、实体身份验证确认对端是谁。

单向 TLS 在配置上很简单,Broker 配置服务器证书,客户端配置 CA 证书去验证 Broker 的身份,和访问 HTTPS 网站差不多。但前面说了,MQTT 场景里设备身份通常是不可信的低功耗终端,所以更稳妥的做法是双向 TLS。我的经验是,只要设备端能支持客户端证书,就优先上双向 TLS。设备出厂前把唯一签发的客户端证书写进安全存储区,Broker 按证书链校验客户端身份,比用户名密码可靠一个数量级。

这里涉及一个敏感点:TLS 的版本和密码套件选择。截止目前,TLS 1.0 和 TLS 1.1 都已经明确不安全,线上至少也要开 TLS 1.2,有条件的直接把 TLS 1.3 拉满。密码套件方面,优先用 ECDHE 密钥交换套件,既保证前向保密,兼容性也好。我踩过的一个坑是某些老设备的嵌入式 TLS 库只支持 RSA 密钥交换,协商出来的套件非常老,性能差就算了,还总被安全扫描工具报告风险。后来统一方案是在网关层做 TLS 终止,老设备走内网明文 MQTT,网关再统一用高版本 TLS 与云端 Broker 通信,才把这个问题解决掉。

2.2 认证机制:用户名密码、客户端证书与 Token 三种方案选型

MQTT 认证的三种主流方案我再拆细一点。

用户名密码认证是最简单的方案,成本低兼容性最好。但这里有个关键点:在启用 TLS 之前,用户名密码是明文传输的,所以密码认证必须和 TLS 配合使用,否则等于没穿裤子出门。密码本身的存储也有讲究,Broker 端不要存明文,要用哈希。MQTT 5.0 里引入的增强认证机制支持 SCRAM,这比直接把密码哈希存到数据库里安全得多,能有效防止彩虹表攻击。我个人的实践是用外部数据库统一管理凭证,Broker 通过认证插件对接,密码字段存 SCRAM 格式。

客户端证书认证是物联网设备身份验证的“正路”。每台设备出厂时烧录一份由物联 CA 签发的证书,证书的 Common Name 或 Subject Alternative Name 里带上设备唯一标识。Broker 校验客户端证书的签名链、有效期、吊销状态,同时还可以把证书里的设备标识映射成 MQTT 的 Username,实现证书与业务账号的自动绑定。没了密码的维护成本,设备接入体验也好。当然,设备端证书私钥的存储是另一个大问题,能放安全芯片或 TPM 里就不要放普通文件系统。

Token 认证适合那些设备身份动态变化的场景,比如临时接入的调试工具、多租户应用。JWT 这类自包含 Token 在 MQTT 里用起来很方便,Broker 只需要校验签名和有效期,不需要查库。但 Token 认证有个风险是吊销难,Token 一旦签发出去,在过期之前基本没办法阻止它被使用。这个不用过分担心,Token 的过期时间调短一点,配合设备重启时重新换取 Token 的机制,就能把风险控制住。

2.3 Topic 授权与访问控制:通配符权限是数据泄露的重灾区

认证解决的是“你是谁”的问题,授权解决的是“你能干什么”的问题。在 MQTT 里,“干什么”就两件事:发布什么 Topic、订阅什么 Topic。

Topic 访问控制的第一原则是“最少权限”。给设备分配权限时,只放行它业务上必须用到的 Topic 路径。比如车牌识别终端只需要发布某个前缀下的识别结果 Topic,那就只给它发这个前缀的发布权限;它不需要订阅任何命令消息,那就不要给它任何订阅权限,或者只允许订阅一个特定前缀的确认消息。

通配符是这里最大的坑。MQTT 的 Topic 支持 + 和 # 通配符,很多人图省事直接给所有客户端配了 subscribe # 的超高权限。更夸张的是在网关上用同一个账号转发光所有场景的数据。每次看到这种配置我都头皮发麻——这意味着任何拿到这个账号的客户端都能订阅整个 Broker 上所有设备的消息。完成一个 MQTT 授权配置前,我会要求自己把权限表打印出来逐条审查,凡是出现 # 通配符的地方都必须写明业务理由。

Topic 路径的命名结构本身就是一种安全设计。常见的做法是采用 /{租户}/{设备类型}/{设备ID}/{数据类别} 这样的层级,然后利用 ACL 规则按前缀匹配。只要 Topic 规划合理,ACL 的规则写起来就非常清晰,也不容易误伤其他业务线。反过来,Topic 命名乱糟糟的话,配 ACL 简直是一场噩梦,规则要么放太宽,要么互相覆盖。

2.4 MQTT 5.0 给安全带来的实质升级

很多团队到现在还在用 MQTT 3.1.1,其实 5.0 在安全方面的升级是非常值得迁移的。最明显的是增强认证机制引入了 AUTH 报文,支持 SCRAM 这类质询响应式认证,密码不再以可重放的哈希值在线路上传递。任何一次成功认证尝试产生的 AUTH 数据只对该次会话有效,重放攻击基本被堵死了。

5.0 还引入了用户属性、会话过期等新特性,允许你在 CONNECT 报文的用户属性字段里塞进设备管理信息,让认证逻辑可以做更精细的上下文章控。同时,MQTT 5.0 的 Reason Code 机制让 Broker 在拒绝连接时能返回具体原因,排障体验比 3.1.1 强太多——不用再面对一个笼统的“连接被拒绝”然后挨个去猜了。

从 3.1.1 迁移到 5.0 在协议层面成本并不高,主要是客户端的库要换支持 5.0 的版本。我现在做新项目直接要求 MQTT 5.0起步,存量项目也已经开始灰度迁移。安全这件事,别让协议版本成为拖后腿的短板。

3. 完整实操:搭一套带双向 TLS 认证和细粒度 ACL 的 MQTT 环境

3.1 证书体系规划与 OpenSSL 生成命令

先确定证书层级。最简单也最灵活的是三级:根 CA 证书签发一个中间 CA 证书,中间 CA 证书用来签发 Broker 服务端证书和每一台设备的客户端证书。为什么要插一层中间 CA?因为根 CA 是整个信任链的源头,私钥应该离线保存在安全的地方,日常签发的操作都用中间 CA 的私钥。万一中间 CA 泄露了,吊销中间 CA 再重新签发下级证书,不用动根 CA。

证书生成我直接给一套能用的命令。生成根 CA 私钥和自签名证书:

# 生成根CA私钥 openssl genrsa -out ca.key 4096 # 生成根CA自签名证书,有效期10年 openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \ -subj "/CN=My IoT Root CA" -out ca.crt

然后是签发中间 CA:

# 生成中间CA私钥 openssl genrsa -out intermediate.key 4096 # 生成中间CA证书签名请求 openssl req -new -key intermediate.key \ -subj "/CN=My IoT Intermediate CA" -out intermediate.csr # 用根CA签发中间CA证书 openssl x509 -req -in intermediate.csr \ -CA ca.crt -CAkey ca.key -CAcreateserial \ -days 3650 -sha256 \ -extfile <(printf "basicConstraints=critical,CA:TRUE\nkeyUsage=critical,keyCertSign,cRLSign") \ -out intermediate.crt

服务端证书要注意 Subject Alternative Name,别只填 Common Name。现代 TLS 库默认检查 SAN,只填 CN 的证书在某些客户端里会直接验证失败:

# 生成Broker服务端私钥 openssl genrsa -out broker.key 2048 # 生成签名请求 openssl req -new -key broker.key \ -subj "/CN=mqtt.example.internal" -out broker.csr # 用中间CA签发服务端证书,有效期2年 openssl x509 -req -in broker.csr \ -CA intermediate.crt -CAkey intermediate.key -CAcreateserial \ -days 730 -sha256 \ -extfile <(printf "subjectAltName=DNS:mqtt.example.internal,DNS:mqtt.example.com,IP:10.0.0.10\nkeyUsage=critical,digitalSignature,keyEncipherment\nextendedKeyUsage=serverAuth") \ -out broker.crt

设备证书签发时,我建议把设备标识写进 SAN 的 URI 字段里,看起来是这样:URI:device://plate-recognizer-001。这样 Broker 在验证明书后能直接从证书里提取设备身份并映射到 MQTT 账号,审计日志也能用同一个 ID 维度去梳理。

签发客户端证书的命令:

# 生成设备私钥 openssl genrsa -out device001.key 2048 # 签名请求 openssl req -new -key device001.key \ -subj "/CN=device001" -out device001.csr # 用中间CA签发设备证书 openssl x509 -req -in device001.csr \ -CA intermediate.crt -CAkey intermediate.key -CAcreateserial \ -days 825 -sha256 \ -extfile <(printf "subjectAltName=URI:device://plate-recognizer-001\nkeyUsage=critical,digitalSignature\nextendedKeyUsage=clientAuth") \ -out device001.crt

这套证书体系里有几个配置文件层面的点容易踩坑。签发服务端证书时 extendedKeyUsage 必须带 serverAuth,签发设备证书时必须带 clientAuth,搞反了证书装上也会报用途错误。还有设备证书有效期建议不超过 825 天,这是为了兼容那些只认最长有效期限制的老客户端库,有效期太长的证书在部分限制较严的TLS实现里会被直接拒绝。

3.2 Broker 端安全配置:TLS、认证和 ACL 三个维度

以 Mosquitto 为例,把刚才生成的证书体系落到配置里。TLS 部分:

# /etc/mosquitto/conf.d/secure.conf listener 8883 0.0.0.0 # 证书配置 cafile /etc/mosquitto/certs/ca.crt certfile /etc/mosquitto/certs/intermediate.crt keyfile /etc/mosquitto/certs/broker.key # 使用TLS 1.2及以上 tls_version tlsv1.2 # 双向TLS认证,客户端必须提供有效证书 require_certificate true use_identity_as_username true

注意 cafile 这里指向根 CA 证书,用来验证客户端证书链。certfile 指向的是中间 CA 证书和 Broker 证书的合并文件,因为有些 MQTT 客户端只加载这一份证书文件去验证服务器身份,不主动去拉中间证书。我一般把 Broker 证书和中间 CA 证书拼成一个 bundle 文件:

cat intermediate.crt broker.crt > broker-bundle.crt

认证层面,Mosquitto 可以直接用密码文件,但我更推荐用认证插件对接外部系统。项目里常用的方案是把密码哈希放在数据库中,通过 HTTP 认证插件把 MQTT 连接时的用户名密码和证书信息转发到认证服务,由认证服务返回允许或拒绝。

ACL 配置是最后一道闸。拿一个车场车牌识别场景举例,识别终端只允许向特定前缀发布数据,而且只能订阅自己设备ID前缀下的命令回执:

# /etc/mosquitto/acl.conf acl_file /etc/mosquitto/acls # /etc/mosquitto/acls # 拒绝所有客户端的默认访问 acl deny # 车牌识别终端只允许发布自己的识别结果 user plate-001 topic write parking/entrance/plate-001/recognized topic read parking/entrance/plate-001/command/ack user plate-002 topic write parking/entrance/plate-002/recognized topic read parking/entrance/plate-002/command/ack # 中台服务可以订阅所有识别结果 user gateway-backend topic read parking/entrance/+/recognized

ACL 文件里的 user 取出的是证书里的 Common Name,因为前面配置了 use_identity_as_username true。这就是双向 TLS 和授权联动起来的效果,设备证书的 CN 直接作为用户名参与权限规则匹配,运维不用额外维护一套账号密码和设备的对应关系。

很多团队用的是 EMQX 或 NanoMQ 这类更重的 Broker,配置界面更丰富。原理一个样:认证器配置双向 TLS,授权器配置基于 Topic 前缀的访问规则。把 Mosquitto 这套原理吃透,换其他 Broker 只是换个界面的问题。

3.3 客户端连接配置:Python、C# 和 WebSocket 桥接的差异

客户端侧最容易出的问题就是证书链加载不完整。用 Python 的 paho-mqtt 库写一个带双向 TLS 的客户端:

import paho.mqtt.client as mqtt client = mqtt.Client(client_id="plate-001", protocol=mqtt.MQTTv5) # 配置CA证书、客户端证书和私钥 client.tls_set( ca_certs="/path/to/ca.crt", certfile="/path/to/device001.crt", keyfile="/path/to/device001.key", tls_version=mqtt.client.ssl.PROTOCOL_TLSv1_2 ) # MQTT 5.0增强认证示例 client.username_pw_set("plate-001", password=None) client.connect("mqtt.example.internal", port=8883, keepalive=60) client.loop_start() client.publish("parking/entrance/plate-001/recognized", payload='{"plate":"京A12345","time":"...","confidence":0.98}', qos=1)

C# 里用 MQTTnet 的话,配置思路一致:

var factory = new MqttFactory(); var client = factory.CreateMqttClient(); var options = new MqttClientOptionsBuilder() .WithTcpServer("mqtt.example.internal", 8883) .WithTlsOptions(o => { o.UseTls(true); o.WithSslProtocols(SslProtocols.Tls12 | SslProtocols.Tls13); o.WithCertificateValidationHandler(ctx => { // 这里做证书链和SAN校验 return ValidateServerCertificate(ctx.Certificate); }); o.WithClientCertificate(new X509Certificate2("device001.pfx")); }) .WithClientId("plate-001") .Build(); await client.ConnectAsync(options);

WebSocket 场景要特别注意证书终止的位置。浏览器里跑 MQTT over WebSocket 时,TLS 连接通常终止在 Web 网关层而不是 MQTT Broker 层。也就是说你需要在 Nginx 或 API 网关上配置 TLS 证书,然后用反向代理把 WebSocket 流量转发给后端 Broker。此时网关负责对浏览器端的 TLS 握手,内部网关到 Broker 之间是内网明文连接,安全边界就划在网关上。

很多项目的疏漏在于网关和 Broker 之间的内网链路不加任何保护,觉得内网就是安全的。实际上内网横向渗透的成本极低,任何一台被攻破的旁路设备都可能通过 ARP 欺骗或端口扫描抓到这段明文 MQTT 流量。稳妥做法是网关到 Broker 之间至少走 TLS,或者干脆让网关支持双向 TLS 直接代理。

3.4 安全验证:站在攻击者视角测试配置是否真的生效

配置做完不等于安全到位。我会从三个角度去验证:

第一,抓包看加密是否生效。在客户端侧跑 tcpdump 或者用 Wireshark,如果 8883 端口上的数据流是 TLS 密文,CONNECT 报文的用户名和密码已经不可见,说明传输加密在工作。如果抓包还能看到明文的 MQTT 报文或者用户名密码字段,那说明客户端和 Broker 之间的链路某些环节没有真正启用 TLS,比如客户端实际连了 1883 端口。

第二,带错误凭证去连接。拿一把没有签发的自签名客户端证书去连接 Broker,预期结果是被拒。拿合法的客户端证书但发布一个 ACL 之外的 Topic,预期结果是 Broker 返回认证失败或直接断开。这一步能同时验证 Broker 的认证插件和 ACL 规则有没有真正加载。

第三,用通配符订阅越权数据。让一个只有单点写权限的客户端尝试去订阅 parking/# 或者 parking/+/recognized,如果它能订阅成功,说明 ACL 配置有问题——最常见的原因是 acl deny 默认策略被后续的 allow 规则覆盖了,或者 use_identity_as_username 没有生效导致 user 名称匹配不上。我做过的最有效的一个测试方法,是把整个 ACL 文件规则逐条在文档里模拟一遍,想象自己是攻击者,找到匹配路径,然后再看有没有规则的顺延覆盖。

4. 常见问题排查与实战避坑记录

4.1 证书验证失败:九成是 SAN、证书链和时间戳三个原因

双向 TLS 配置好之后遇到最多的坑 ,就是客户端报告证书验证失败,但服务端日志看不出明显问题。

第一个常见原因是证书的 Subject Alternative Name 缺失或不匹配。老做法是在 CN 里填 IP 或域名,新 TLS 库默认只检查 SAN。我碰到过一个项目,Broker 证书的 CN 填了内网 IP,但客户端连接时用的域名,SAN 里没有这个域名,TLS 握手直接失败。解决方案是签发证书时把域名、IP 都写进 SAN,客户端校验逻辑里明确接受哪几个主机名。

第二个是证书链加载顺序。服务端向客户端发送证书链时,必须按“叶子证书在前、中间 CA 在后”的顺序。如果 broker.crt 和 intermediate.crt 的顺序反了,很多客户端直接拒接。用前面提到的 broker-bundle.crt 合并文件就能规避,但要注意合并顺序一定是中间 CA 在前叶子证书在后。

第三个隐蔽且常见的问题是设备时间不同步。证书校验依赖当前时间在证书有效期范围内,工厂车间的一批嵌入式设备长时间不联网,时钟漂移几个月,TLS 握手就会报证书有效期异常。不要觉得这是低级问题,我在现场排查过好几次,最后都是因为设备主板上的实时时钟电池没电了。解决问题很简单,给设备上 NTP 同步,同时在监控里加上对设备时钟偏移量的告警。

4.2 认证与授权配置里那些“看似正常实则裸奔”的坑

很多 Misconfiguration 比不配更危险。

第一个坑是用户名密码认证没有搭配 TLS 加密。有些协作者觉得 MQTT 的用户名密码已经能“验证身份”,却不理解这些凭证在明文传输时等同于直接暴露。抓包抓到 CONNECT 报文的那一刻,用户名密码就已经泄了。哪怕 Broker 端有复杂的 ACL,凭证泄露后攻击者直接拿合法账号登录,ACL 形同虚设。

第二个坑是默认允许订阅所有 Topic。某些 Broker 的默认规则是所有客户端可以自由订阅任何 Topic,如果 ACL 文件没有显式的 deny 规则,那“配置了 ACL”只是一个心理安慰。想让拒绝成为默认策略,必须像示例中一样在 ACL 文件开头写上 acl deny,再去开白名单。这一点测试里要专门验:新注册的客户端在没有匹配规则时,订阅任何 Topic 都应该被拒。

第三个坑是客户端 ID 冲突与窃取。如果只做用户名密码认证不做双向证书,攻击者可以在网络上窃听一个合法设备的 ClientID,然后自己用相同的 ClientID 去连接 Broker,把合法设备踢下线。所以客户端 ID 必须和认证信息强绑定,双向 TLS 的证书 SAN 里携带设备唯一标识正是为了防这个。审计日志也要记录每个连接的证书指纹,方便事后追溯。

4.3 安全与性能、兼容性的权衡:哪些钱不能省,哪些可以屈就

安全配置的代价是握手开销增加、证书管理变复杂、部分老设备不兼容。实际操作中要学会分类处理。

资源受限设备上跑完整双向 TLS 确实有压力,一个 2048 位 RSA 握手会让某些低端 MCU 卡顿明显。常规解法是采用 ECC 证书代替 RSA 证书,同样的安全强度下密钥长度只有 256 位,握手计算量小一个量级。还有就是把设备从全双向 TLS 降级为单向 TLS 加 Token 认证的组合,前提是设备的密钥存储足够安全且 Token 生命周期很短。我一般不推荐为了性能牺牲正向的安全机制,但是在兼容性和安全性确实冲突时,有一套“最小安全基线”很重要:必须加密、必须认证、必须有权验,三者缺一不可,其他能力再按设备能力取舍。

另一个值得提的点是 TLS 会话恢复。频繁断线重连的物联网设备如果每次重连都重新做一次完整握手,延迟和开销都是浪费。服务端启用 TLS 会话缓存,客户端在较短时间内的重连可以直接复用会话密钥,效果立竿见影。对电池供电的 NB-IoT 设备来说,这个配置能让待机时间明显提升。

4.4 混合协议架构下的安全衔接:HTTP、TCP、串口和 WebSocket 的边界

在实际项目里,MQTT 往往不是唯一的通信协议。一个典型的场景是这样:云平台和边缘网关之间用 MQTT over WebSocket 或 MQTT over TCP 通信,边缘网关向下用 S7.NET 去读西门子 PLC 的数据,同时在本地用串口或 USB 连接某些非网口设备,车牌识别摄像机走 HTTP 推送,某些传感器设备用 UDP 上报。这时候安全的重点不是给每个协议单独做一套高级配置,而是把边界理清楚。

我的经验是给每个协议通道标定“安全属性”:加密与否、认证强度、信任边界。MQTT 走 TLS 是通道内的安全;S7.NET 连 PLC 通常是厂区内网,靠 VLAN 隔离和防火墙白名单做边界保护;UDP 上报更适合做一个独立的边缘接收服务,校验来源 IP 和报文签名后再转为 MQTT 向云端转发。串口和 USB 这类物理链路要防的是物理接触,把串口调试口禁掉,把 USB 接入策略收严即可。

其中最容易出问题的是 HTTP 和 MQTT 之间的转换。车牌识别设备通过 HTTP POST 推送到本地服务,本地服务再转成 MQTT 发布到 Broker。如果 HTTP 那一段没有开启 HTTPS,识别结果在链路上就是裸奔的。别忘了给 HTTP 这一侧也配上 TLS,或者限定为内网访问。安全方案是跟着数据路径走的,只要数据还在裸奔,MQTT 侧做得再漂亮也补不上漏洞。

我见过最典型的一个项目事故是这么来的:设备厂商把车牌号识别结果用 HTTP 明文 POST 到一个内网服务,内网服务再转成 MQTT 发给省平台的 Broker。内网被横向攻破后,攻击者整整两周时间都在旁路窃听车牌数据。安全审计报告打回来,问题根源不在 MQTT,而在上游的 HTTP 明文推送。后来改成设备端通过网关的 HTTPS 接口上报、网关和上游 Broker 之间启用双向 TLS,整套链路才算是真正封口了。这类教训说明,安全永远是链路整体的工程,不能只盯着其中一个协议使劲。

做了这么多年的 IoT 项目,我最大的体会是 MQTT 安全机制本身并不神秘,它的所有设计都是为了回答四个问题:数据在传输路上是否会被偷看、设备接入时是否验证过身份、设备能访问的数据范围是否受控、链路中是否有可追溯的审计记录。这些问题想清楚了,配置证书和 ACL 时就非常有底。哪怕是已经跑起来的存量项目,我也建议至少先把 1883 明文端口关掉,把所有连接迁到 8883 的 TLS 端口,同时把默认的弱口令全部强制重置——这两步做完,你的 MQTT 系统就已经比市面上大半同类项目安全了。后续再逐步推进双向证书、细粒度 ACL 和 MQTT 5.0 迁移,整个安全体系就能真正落地。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进