1. MQTT安全治理全景

很多团队在部署MQTT时,第一反应是“先把消息跑通”。Broker启动、设备连上、Topic能收发,似乎任务就完成了。但真正上线后才会发现:谁都能连、谁都能订阅、谁都能往任意Topic发消息,系统其实处于“裸奔”状态。MQTT安全治理要解决的,正是把“能连、能发、能收”变成“该连的才能连、该发的才能发、该收的才能收”,并且整个过程可追溯。

要理解这件事,先要看清MQTT连接与消息流转的完整路径。一个客户端从建立连接到消息被路由到订阅者,中间会经过多个可以施加控制的“关卡”。这些关卡按时间顺序大致分为三段:连接建立阶段、授权判定阶段、消息路由与审计阶段。身份认证(Authentication)、授权(Authorization,即ACL)、审计(Audit)三者分别对应这三段,它们不是并列的三个功能,而是前后衔接、职责边界清晰的三道防线。

认证回答“你是谁”,授权回答“你能做什么”,审计回答“你做过什么”。认证发生在连接建立时,决定是否允许这个客户端接入;授权发生在每一次订阅和发布时,决定这个动作是否被允许;审计则贯穿全程,记录谁在什么时候连过、订阅过、发布过什么,用于事后追溯和异常发现。

下面这张图把MQTT从连接建立到消息路由的完整链路,以及认证、授权、审计三个控制点的位置和边界画了出来。它可以帮助你建立“控制点在哪里、各自管什么”的整体认知。

MQTT 安全治理全景:认证 · 授权 · 审计 控制点 客户端侧 设备 A clientId: dev-A-01 设备 B clientId: dev-B-02 应用服务 clientId: app-svc-01 运维终端 clientId: ops-01 未知客户端 无凭证 / 伪造 ① 身份认证 CONNECT 报文到达 Username / Password 校验凭证有效性 Client Certificate TLS 双向证书校验 JWT / Token 签名与有效期验证 认证通过 ✓ 认证拒绝 ✗ 拒绝时返回 CONNACK reason code: 0x04/0x05 认证只决定“能否接入” 不决定“能操作什么” ② 授权 ACL 每次 SUBSCRIBE / PUBLISH Topic 白名单匹配 dev-A-01 → sensors/A/# 仅允许 publish 操作类型判定 read / write / readwrite 按 clientId 或 username 绑定 通配符与优先级 + 单层 / # 多层 deny 优先于 allow 允许操作 ✓ 拒绝操作 ✗ 授权发生在认证之后 每次操作独立判定 不依赖连接状态 ③ 消息路由 Broker 核心处理 Topic 匹配 订阅树查找 消息投递 QoS 0/1/2 处理 保留消息 Retained / Will 审计日志 连接/断开记录 订阅/发布记录 拒绝事件记录 审计不阻断操作 但提供追溯依据 异常检测与合规 认证 = 连接时校验身份,决定是否接入 |  授权 = 每次操作校验权限,决定是否放行 |  审计 = 全程记录行为,用于追溯与合规

从图中可以看到,认证和授权虽然经常被放在一起讨论,但它们的触发时机和职责完全不同。认证只在CONNECT报文到达时执行一次,通过后客户端进入已连接状态;而授权在每一次SUBSCRIBE和PUBLISH时都会重新判定,即使连接已经建立,也不代表后续操作一定被允许。审计则像一条贯穿始终的线,不直接阻断操作,但为事后分析提供依据。

认证:连接建立时的第一道门

MQTT协议本身在CONNECT报文中提供了几个关键字段:Client Identifier(clientId)、Username、Password,以及可选的Will Topic和Will Message。认证就是围绕这些字段展开的。常见的认证方式有三种:

  • 用户名密码认证:Broker维护用户表,校验Username和Password是否匹配。适合内部系统或设备数量可控的场景。密码应以哈希形式存储,避免明文。
  • 双向TLS证书认证:客户端和服务端各自持有证书,在TLS握手阶段完成身份校验。安全性最高,适合对安全要求严格的物联网场景,但证书管理成本也最高。
  • Token认证:客户端携带JWT或其他签名Token,Broker验证签名和有效期。适合与现有身份系统(如OAuth2)集成,Token可以携带过期时间和权限声明。

认证失败时,Broker会返回CONNACK报文,其中reason code为0x04(Bad User Name or Password)或0x05(Not Authorized)。客户端收到后应停止重连或进入退避策略,而不是无限重试。这一点在实际运维中很重要:如果设备凭证配置错误,无限重连会迅速消耗Broker资源。

实践建议:clientId应具有唯一性和可识别性,例如“产品线-设备类型-序列号”的格式。避免使用随机字符串或重复的clientId,否则会导致连接互踢,也会让审计日志难以关联到具体设备。

授权:每次操作都要过的关卡

授权解决的是“这个客户端能对哪些Topic执行哪些操作”。MQTT的授权通常以ACL(Access Control List)的形式实现,每条规则包含四个要素:主体(clientId或username)、Topic过滤器、操作类型(publish/subscribe/readwrite)、允许或拒绝。

以EMQX为例,一条典型的ACL规则可以写成:

# 允许设备A向自己的传感器Topic发布数据
{allow, {clientid, "dev-A-01"}, publish, ["sensors/dev-A-01/#"]}.

# 允许应用服务订阅所有设备的传感器数据
{allow, {clientid, "app-svc-01"}, subscribe, ["sensors/+/data"]}.

# 拒绝所有客户端订阅系统内部Topic
{deny, all, subscribe, ["$SYS/#", "internal/#"]}.

这里有几个关键点值得注意。第一,Topic过滤器中的“+”匹配单层,“#”匹配多层,但“#”只能出现在末尾。第二,deny规则的优先级通常高于allow,这意味着即使前面有allow规则,只要后面有匹配的deny规则,操作仍会被拒绝。第三,ACL规则是按顺序匹配的,一旦命中就停止继续查找,因此规则的顺序会影响最终结果。

授权失败时,Broker对PUBLISH的处理通常是静默丢弃或断开连接(取决于配置),对SUBSCRIBE则返回SUBACK,其中对应Topic的reason code为0x87(Not authorized)。客户端需要能够识别这些返回码,而不是简单地认为“消息发出去了就成功了”。

审计:不阻断操作,但让一切有迹可循

审计记录的是“谁在什么时候做了什么”。在MQTT场景中,值得记录的审计事件包括:连接建立与断开(含clientId、IP、认证结果)、订阅与取消订阅(含Topic和结果)、发布消息(含Topic、QoS、消息大小)、授权拒绝事件、异常断开(如Keep Alive超时)。

审计日志的价值在故障排查和安全事件回溯中尤为明显。例如,当发现某个Topic收到异常数据时,可以通过审计日志快速定位是哪个clientId在什么时间发布的;当怀疑有未授权访问时,可以查看认证拒绝和授权拒绝的记录,判断是否存在暴力尝试或配置错误。

注意边界:审计不等于监控告警。审计负责“记录”,监控负责“发现异常并通知”。两者可以共用数据源,但职责不同。不要因为有了审计日志就认为安全治理已经完成,还需要配套的告警规则和定期审查机制。

三者的关系与边界

把认证、授权、审计放在一起看,它们构成了一条从“入口”到“操作”再到“追溯”的完整链条。认证是入口控制,授权是操作控制,审计是事后控制。三者缺一不可:只有认证没有授权,已连接客户端可以随意操作;只有授权没有认证,攻击者可以伪造身份绕过ACL;只有认证和授权没有审计,出了问题无法定位和追责。

在实际部署中,建议按照以下顺序建立治理能力:先确保认证覆盖所有客户端,禁止匿名连接;再根据最小权限原则配置ACL,从“默认拒绝”开始逐步放开;最后开启审计日志并配置保留策略。这个顺序不是绝对的,但符合“先关门、再分权、后记录”的治理逻辑。