☰
物联网平台安全实战:一机一密认证与ACL权限控制
2026/10/8 12:14:49 网站建设 项目流程

1. 从一个真实事故说起:设备数据被伪造的代价

去年帮一个做智慧农业的团队排查问题,他们的土壤墒情监测系统上线三个月,突然发现某片区域的灌溉决策完全乱套——明明传感器上报湿度只有20%,实际地里已经涝了。查了两天日志才定位到问题:有人用一台普通开发板,伪造了十几个"合法"设备的身份,往平台里灌了一堆假数据。更离谱的是,这些假设备还能订阅到其他真实设备的上报主题,等于把整个片区的数据都看光了。

这个事故暴露的其实是物联网平台最基础也最容易被忽视的两道门:身份认证和权限控制。很多团队在开发阶段图省事,EMQX 直接开匿名接入,设备连上来就能发数据,等业务跑起来再想补安全,发现存量设备已经几千台,改造成本高得吓人。

这篇就围绕一机一密认证 + ACL 权限这套组合拳,用 Spring Boot 做认证后端、EMQX 做消息接入,把从设备接入到主题授权的完整链路讲透。适合正在搭物联网平台的后端开发、负责设备接入的嵌入式工程师,以及需要给现有平台补安全短板的运维同学。不管你是刚接触 MQTT 的新手,还是已经用过 EMQX 但没深挖过认证机制的老手,下面这些实操细节和踩坑记录应该都能对上你的场景。

2. 为什么是"一机一密 + ACL"这套组合

2.1 先搞清楚物联网平台到底在防谁

在聊方案之前,得先明确威胁模型。物联网平台的安全威胁大致分三类:设备身份伪造(别人冒充你的设备发数据)、越权访问(设备 A 偷看设备 B 的数据)、重放攻击(截获合法报文重复发送)。这三类里,前两类是绝大多数中小平台最先撞上的,也是本文方案主要解决的。

一机一密解决的是"你是谁"的问题——每台设备出厂时烧录唯一的设备 ID 和密钥,接入时用这对凭证换取出入证。ACL 解决的是"你能干什么"的问题——即使身份合法,也只能发布和订阅自己被授权的主题。两者缺一不可:只有认证没有 ACL,一台被逆向的设备就能监听全网;只有 ACL 没有认证,权限规则根本无从绑定到具体设备。

2.2 为什么不用共享密钥或者用户名密码

我见过不少团队图快,所有设备共用一个 MQTT 用户名密码。这种方案在设备数量少、且设备固件不会被提取的前提下勉强能用,但只要有一台设备被拆机读出凭证,整个平台就等于裸奔。而且共享密钥没法做精细化的 ACL——你没法区分"这台设备只能发自己的数据"和"那台设备可以发自己的数据",因为它们在平台眼里是同一个身份。

一机一密的核心价值在于凭证与设备物理绑定。设备 ID 通常用芯片唯一 ID、MAC 地址或者出厂序列号,密钥在产线烧录时写入安全存储区。这样即使某台设备的密钥泄露,影响范围也被限制在单台设备,平台侧只需要吊销这一台的凭证即可。

2.3 EMQX 的认证链和 ACL 链是怎么走的

EMQX 5.x 的认证和授权是两条独立的链路。设备 CONNECT 时先走认证链,按配置的顺序依次尝试认证器(比如先查 HTTP 认证,再查内置数据库),任一通过即放行。连接建立后,每次 PUBLISH 或 SUBSCRIBE 都会走 ACL 链,同样按顺序匹配规则,命中即执行允许或拒绝。

这个设计的好处是认证和授权解耦。认证可以对接你现有的用户系统(比如 Spring Boot 写的设备管理服务),ACL 可以用 EMQX 内置的规则引擎,也可以用外部 HTTP 服务动态判断。我下面给的方案是认证走 HTTP 回调到 Spring Boot,ACL 用 EMQX 内置的基于客户端属性的规则,这样在保证安全的同时把性能开销控制住——毕竟每条消息都回调 HTTP 做 ACL 判断,QPS 上万之后延迟会很感人。

3. 一机一密认证的完整实现

3.1 设备凭证的设计与产线烧录

设备凭证至少包含三部分:设备唯一 ID(clientId)、设备密钥(secret)、产品标识(productKey)。clientId 建议直接用"产品标识 + 设备序列号"拼接,比如agri_soil_0001,这样在 EMQX 侧看连接列表时一眼能认出设备归属。密钥用 32 位随机字符串,产线烧录时写入设备的安全存储区(比如 ESP32 的 NVS 加密分区、STM32 的 OTP 区域)。

这里有个容易踩的坑:clientId 不能重复。EMQX 默认允许同一 clientId 重复连接,后连接的会把先连接的踢掉。如果你的设备序列号生成逻辑有 bug 导致重复,会出现设备频繁掉线但日志里看不出明显错误。建议在 Spring Boot 的设备注册接口里对 clientId 做唯一性校验,产线烧录前先调接口注册,注册失败就不烧录。

密钥的存储方式直接决定了方案的安全上限。如果设备固件能被轻易读出,那一机一密就退化成了共享密钥。低成本方案可以用芯片自带的唯一 ID 作为密钥派生因子,比如secret = HMAC(芯片UID, 产品级主密钥),这样即使固件被读,攻击者也拿不到产品级主密钥,无法伪造其他设备。当然这要求芯片有硬件加密模块,纯软件实现的话安全性会打折扣。

3.2 Spring Boot 认证接口的写法

EMQX 的 HTTP 认证器会在设备 CONNECT 时 POST 一个 JSON 到你的接口,包含 clientid、username、password 等字段。Spring Boot 侧需要返回{"result": "allow"}或{"result": "deny"}。下面是一个最小可用的实现:

@RestController @RequestMapping("/mqtt/auth") public class MqttAuthController { @Autowired private DeviceService deviceService; @PostMapping("/connect") public Map<String, String> authenticate(@RequestBody Map<String, String> body) { String clientId = body.get("clientid"); String password = body.get("password"); if (clientId == null || password == null) { return Map.of("result", "deny"); } Device device = deviceService.findByClientId(clientId); if (device == null || !device.isEnabled()) { return Map.of("result", "deny"); } // 密钥比对,实际生产建议用 HMAC 而非明文存储 String expected = hmacSha256(device.getSecret(), clientId); if (!constantTimeEquals(expected, password)) { return Map.of("result", "deny"); } return Map.of("result", "allow"); } }

几个关键点。第一,密码不要明文传输和存储。设备侧用HMAC-SHA256(secret, clientId)算出摘要作为 password,服务端用同样算法验证。这样即使传输被截获,攻击者也拿不到原始 secret。第二,比对要用恒定时间算法,防止时序攻击——虽然对物联网场景来说这个威胁等级不高,但养成习惯没坏处。第三,接口要做限流,防止有人拿大量伪造 clientId 刷你的认证接口,把数据库打挂。

EMQX 侧配置 HTTP 认证器时,URL 填http://your-backend:8080/mqtt/auth/connect,请求方法选 POST,内容类型选 JSON。认证器顺序放在第一位,后面可以跟一个内置数据库认证器作为兜底(比如给运维用的管理账号)。

3.3 设备侧连接代码示例

以 ESP32 用 PubSubClient 为例,连接逻辑大概是这样:

String clientId = "agri_soil_0001"; String secret = "设备烧录时写入的密钥"; String password = hmacSha256(secret, clientId); client.setServer("mqtt.yourdomain.com", 1883); client.connect(clientId.c_str(), clientId.c_str(), password.c_str());

注意 username 这里也填了 clientId,是因为 EMQX 的 ACL 规则里要用到 username 做主题匹配。如果你不想用 username,也可以在 ACL 规则里用%c(clientId 占位符)来匹配主题,效果一样。

提示:生产环境务必用 TLS(8883 端口),否则 password 摘要虽然不能反推 secret,但 clientId 和连接行为会暴露在链路上,给攻击者提供信息。

4. ACL 权限规则的设计与落地

4.1 主题命名规范是 ACL 的地基

ACL 规则写得好不好,一半取决于主题设计。我推荐的主题结构是:

{productKey}/{deviceId}/up 设备上报 {productKey}/{deviceId}/down 平台下发 {productKey}/{deviceId}/cmd 命令响应

这种结构的好处是设备 ID 天然出现在主题里,ACL 规则可以直接用%c或%u占位符匹配。比如规则写成{productKey}/%c/up,EMQX 会自动把%c替换成当前连接的 clientId,只有 clientId 匹配的设备才能发布到这个主题。

如果主题设计成sensor/data这种扁平结构,所有设备都往同一个主题发,ACL 就没法做设备级隔离了,只能整主题允许或拒绝。所以主题规范要在项目初期定死,后期改主题等于所有设备固件都要升级,成本极高。

4.2 EMQX 内置 ACL 规则配置

EMQX 5.x 的 ACL 可以在 Dashboard 的"访问控制"里配,也可以直接改配置文件。内置 ACL 的规则格式是:

{allow, {username, {re, "^agri_soil_.*"}}, publish, ["agri/%u/up"]}. {allow, {username, {re, "^agri_soil_.*"}}, subscribe, ["agri/%u/down", "agri/%u/cmd"]}. {deny, all}.

这几条规则的意思是:用户名匹配agri_soil_开头的设备,允许发布到agri/{自己的clientId}/up,允许订阅agri/{自己的clientId}/down和cmd,其他一律拒绝。最后那条{deny, all}是兜底,非常重要——没有它的话,EMQX 默认行为是允许所有未匹配的操作,等于 ACL 白配了。

这里有个细节:%u是 username 占位符,%c是 clientId 占位符。如果你的设备连接时 username 和 clientId 填的不一样,要确认用哪个。我一般建议两者填一样的值,避免混淆。

4.3 动态 ACL:什么时候需要回调 HTTP

内置 ACL 适合规则固定的场景。但有些业务需要动态判断,比如"设备 A 可以订阅设备 B 的数据,因为它们在同一个农户账号下"。这种关系存在数据库里,内置 ACL 表达不了,就得用 HTTP 授权器。

HTTP 授权器的配置和认证器类似,EMQX 会在每次 PUBLISH/SUBSCRIBE 时 POST 请求到你的接口,body 里包含 clientid、topic、action(publish/subscribe)。Spring Boot 侧查数据库判断是否允许。这种方案灵活但性能开销大,建议只对订阅操作走 HTTP,发布操作走内置 ACL——因为发布频率远高于订阅。

@PostMapping("/mqtt/acl") public Map<String, String> authorize(@RequestBody Map<String, String> body) { String clientId = body.get("clientid"); String topic = body.get("topic"); String action = body.get("action"); if ("subscribe".equals(action) && topic.startsWith("share/")) { // 共享订阅场景,查设备所属农户的授权关系 boolean allowed = aclService.canSubscribeShared(clientId, topic); return Map.of("result", allowed ? "allow" : "deny"); } return Map.of("result", "deny"); }

注意:HTTP 授权器返回 deny 时,EMQX 会直接断开连接还是仅拒绝本次操作,取决于版本配置。EMQX 5.x 默认是拒绝操作但保持连接,这个行为在调试时容易让人困惑——设备看起来连着,但消息发不出去。

5. 联调与压测:把方案跑通再上线

5.1 用 MQTTX 模拟设备做认证测试

方案配好后,别急着烧设备,先用 MQTTX 这类客户端工具模拟。建两个连接:一个用合法的 clientId 和正确密码,一个用错误密码。合法连接应该能成功,错误密码应该被拒绝。然后再测 ACL:用合法设备连接后,尝试发布到别的设备的主题,应该被拒绝。

我习惯用 MQTTX 的脚本功能批量模拟,比如同时起 100 个连接,clientId 从agri_soil_0001到agri_soil_0100,密码用脚本算 HMAC。这样能在几分钟内验证认证接口的并发能力和 ACL 规则的准确性。

5.2 压测时容易暴露的两个问题

第一个是认证接口的响应延迟。EMQX 的 HTTP 认证器默认超时是 5 秒,如果 Spring Boot 接口因为数据库慢查询卡住,设备连接会大面积超时。建议给认证接口加缓存——设备密钥不常变,用 Caffeine 或 Redis 缓存 clientId 到 secret 的映射,TTL 设 5 分钟,能挡掉绝大部分重复查询。

第二个是ACL 规则匹配的性能。内置 ACL 的规则条数如果超过几百条,匹配会变慢。我实测过,规则在 50 条以内时,单节点 EMQX 处理 ACL 匹配的额外延迟在 0.1ms 级别,基本无感;超过 500 条后延迟开始明显。所以规则要精简,能用通配符就别写死列表。

5.3 上线前的安全检查清单

检查项合格标准常见问题
匿名接入已关闭开发环境遗留的 allow_anonymous=true 没改
认证接口有超时和限流数据库慢查询拖垮整个认证链
ACL 兜底规则有 deny all漏配导致未匹配操作默认放行
密钥存储非明文数据库里 secret 字段明文存储
TLS生产启用图省事用 1883 明文端口
日志脱敏密码不落日志认证失败日志里打印了完整 password

这张表我每次上线前都会过一遍,尤其是"匿名接入"和"ACL 兜底"这两项,出问题的概率最高。

6. 踩过的坑和排查技巧

6.1 设备频繁掉线但认证日志显示成功

这个问题的典型原因是clientId 冲突。两台设备用了同一个 clientId,EMQX 默认行为是踢掉旧连接,导致设备反复重连。排查方法是看 EMQX 的连接日志,搜索 "kicked" 关键字。解决方式是在设备注册接口做 clientId 唯一性校验,产线烧录前先注册。

另一个可能原因是keepalive 设置不合理。设备侧 keepalive 设了 60 秒,但网络抖动导致心跳包偶尔丢失,EMQX 在 1.5 倍 keepalive 时间后判定设备离线。建议 keepalive 设 30-60 秒,并在设备侧实现断线重连,重连时用指数退避避免风暴。

6.2 ACL 规则明明配了却不生效

最常见的原因是规则顺序。EMQX 的 ACL 是按顺序匹配的,第一条命中的规则决定结果。如果你把{deny, all}写在了允许规则前面,那所有操作都会被拒绝。规则顺序应该是:具体的允许规则在前,宽泛的拒绝规则在后。

第二个原因是占位符用错。%u匹配 username,%c匹配 clientId,%a匹配 IP 地址。如果你的设备连接时 username 为空,用%u的规则就永远匹配不上。排查时可以在 EMQX Dashboard 的"访问控制"页面看规则命中统计,哪条规则命中次数为 0 就重点查哪条。

6.3 认证接口被刷导致服务不可用

有次客户的平台突然大量设备掉线,查下来是有人用脚本拿随机 clientId 刷认证接口,把 Spring Boot 的数据库连接池打满了。后来加了两层防护:一是认证接口前置 Redis 缓存,未注册的 clientId 直接返回 deny 不查库;二是用 Sentinel 做限流,单 IP 每秒最多 100 次认证请求。加完之后再没出现过类似问题。

提示:认证接口的限流阈值要根据你的设备规模定。10 万台设备同时重连的场景下,认证 QPS 可能瞬间到几千,限流阈值设太低会误伤正常设备。建议按"设备总数 / 重连窗口秒数"估算,再留 2 倍余量。

6.4 共享订阅场景下的 ACL 特殊处理

共享订阅($share/group/topic)的 ACL 规则和普通订阅不一样,因为主题里多了$share前缀。如果你的 ACL 规则写的是agri/%c/down,共享订阅的主题$share/g1/agri/device1/down就匹配不上。解决办法是在规则里显式加上共享订阅的格式,或者用 HTTP 授权器动态判断。

这个坑我在一个多租户项目里踩过,当时设备订阅用的是共享订阅做负载均衡,结果 ACL 一直拒绝,查了半天才发现是主题前缀的问题。后来在规则里加了一条{allow, {username, {re, "^agri_.*"}}, subscribe, ["$share/+/agri/%u/down"]}才解决。

7. 方案的可扩展方向

这套一机一密加 ACL 的方案跑通后,还有几个方向可以按需扩展。设备证书双向认证适合对安全要求更高的场景,用 X.509 证书替代 HMAC 密钥,EMQX 原生支持,但产线烧录成本会上升。动态密钥轮换适合担心密钥长期暴露的场景,设备定期调接口换新密钥,旧密钥保留一个宽限期后失效。审计日志适合有合规要求的项目,把每次认证和 ACL 判断的结果落库,方便事后追溯。

我个人在实际操作中的体会是,安全方案不要一次上太满,先把认证和 ACL 这两道最基础的门守住,把匿名接入关掉,把兜底拒绝规则配上,就已经挡住了 90% 的常见风险。剩下的证书、轮换、审计,等业务量上来、有明确需求了再逐步加,避免过度设计拖慢上线节奏。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询