MQTT 认证详解:密码、JWT、SCRAM 与 AUTH 报文
理解 MQTT 认证机制,逐项配置 Mqttable 客户端的认证方式,并用真实实验区分连接认证、TLS 保护与 Topic 权限。

MQTT 客户端能访问 Broker,不一定能通过连接认证。即使连接成功,订阅某个 Topic 也可能被拒绝。这是两个不同阶段的问题,不能都靠换密码解决。
本文先说明认证机制,再结合 Mqttable 的新建与编辑 Client 表单逐项配置、验证。本文由 Mqttable 发布,使用它作为操作示例;理解和实现这些机制不要求使用 Mqttable。产品截图来自隔离的源码 Web Runtime,凭据均为一次性本地实验材料,不代表桌面安装包、托管服务或生产部署通过验收。
认证确定 Broker 接受的客户端身份。授权决定这个身份可以发布、订阅哪些 Topic,常见实现是 Topic ACL。TLS保护传输并验证服务端身份;双向 TLS 还可以验证客户端证书。
MQTT 5 的 AUTH 报文比“认证”这个词的范围小。它承载 SCRAM 等增强认证交换,不是所有认证方式统一使用的报文。普通密码认证不需要 AUTH;把 JWT 放进 CONNECT 的 Password 字段也不需要 AUTH。MQTT 5 定义交换框架,不要求每个 Broker 都实现同一种密码库、JWT 签发方或 SCRAM 算法。协议依据见 MQTT 5 第 3.15、4.12 节。
这是机制示意图,不是抓包结果。某一层通过,不能证明下一层也通过。
Client ID 也不是密码。它标识 MQTT 会话,不能与其他在线客户端冲突。Broker 可以把它纳入认证或授权策略,但仅知道别人的 Client ID,本身不是安全的身份认证。
Mqttable 在 Broker 中配置传输协议、服务器证书信任和 mTLS,在 Client 中配置用户名、密码和认证方式。CA Certificate 验证的是服务端信任链,不能代替客户端的 MQTT 密码。如果还没到 MQTT 认证阶段就失败,先看 TLS 证书指南。
当前 MQTT 5 Client 表单提供六个选项。切换到 MQTT 3.1 或 3.1.1 时,认证方式下拉会隐藏,但用户名、密码字段仍然存在。这是 Mqttable 的界面边界,不意味着 MQTT 3.1.1 不支持认证,也不意味着它不能通过密码字段传递 Token。
左右滚动表格,查看其余列。
| 方式 | 需要的材料 | MQTT 连接中传递什么 | Broker 前提 |
|---|---|---|---|
| 无(None) | 不使用 MQTT 密码;匿名示例还要清空用户名 | 不发送密码,非空用户名仍可能发送 | 明确允许该客户端不带密码连接 |
| 密码(Password) | Broker 签发的用户名、密码,或密码 SecretRef | CONNECT 的 Username、Password | 对应的密码认证器和账号 |
| JWT | 完整签名 Token;按策略填写用户名 | 此模式把 Token 放进 CONNECT 的 Password | 从 Password 读取 JWT,认可签名密钥和 claims |
| SCRAM-SHA-1 | 用户名、密码 | SCRAM 属性和挑战响应数据,不使用普通 CONNECT 密码字段 | 明确支持这一旧版机制 |
| SCRAM-SHA-256 | 用户名、密码 | 使用精确方法名的 MQTT 5 增强认证 | 支持 SCRAM-SHA-256 |
| SCRAM-SHA-512 | 用户名、密码 | 使用精确方法名的 MQTT 5 增强认证 | 支持 SCRAM-SHA-512 |
按运营方配置的机制选择,不是哪个名字看着更强就选哪个。SHA-256 和 SHA-512 是两个不同的 SCRAM 方法,客户端不能自行替换。本地 EMQX 6.0.0 实验支持这两种变体,不支持 SHA-1。这是实验 Broker 的边界,不代表 Mqttable 没有 SHA-1 选项。EMQX 的 SCRAM 文档列出了支持的哈希算法。
这六个客户端选项也不是所有 MQTT 部署的身份系统清单。Broker 可以验证证书、调用 HTTP 认证服务或查询数据库。客户端通常只需要知道这些服务要求的报文机制和凭据。
只有 Listener 允许时才用 None。本实验的匿名 Mosquitto Listener 仅绑定 127.0.0.1,不是公网匿名服务。
- 保存指向这个 Listener 的 Broker,打开新建 Client。
- MQTT 版本选 5.0,认证方式选无(None)。
- 清空用户名。密码控件会禁用;选择 None 只抑制密码,不一定抑制用户名。
- 使用唯一 Client ID,点击测试连接。

None 描述客户端如何处理密码。这个身份能不能连接,仍由 Broker 决定。
选择密码(Password),填写运营方提供的 MQTT 用户名,单次测试可以选择明文密码(Raw password)。云控制台账号不一定就是 MQTT 账号,要以部署里的 MQTT 认证设置为准。

Client 的测试连接使用当前表单,包括尚未保存的凭据;截图不会显示密码原文。
CONNECT 中的用户名和密码不会给传输加密。凭据经过不可信网络时,使用正确验证服务器身份的 mqtts 或 wss 端点。本文明文 Listener 仅用于 loopback 实验,不是生产安全配置建议。
需要重复使用时,先在**凭据(Credentials)**中创建 Password Secret,再到 Client 选择 Saved SecretRef。选择器显示受保护引用的元信息,不显示原文。引用缺失或已删除,不是可以忽略的“空密码”。保存边界见凭据与 TLS。

SecretRef 改变 Mqttable 从哪里取得凭据,不改变 Broker 的密码策略,也不会给 CONNECT 加密。
JWT 是 Token 格式,不是新的 MQTT 报文。Mqttable 的这个模式把 Token 放进 Password。Broker 也要从同一字段读取 JWT。有些 Broker 支持从 Username 读取,但选择 Mqttable 的 JWT 模式不会自动切换到那种配置。EMQX 文档分别说明了两种位置。
认证服务已经给出完整 Token 时,直接粘贴到 Password,或保存成 Password SecretRef,不要重新签名。生产环境应按签发方的用户或设备认证流程取得 Token,不能为了让普通客户端自行生成凭据,把签发方的 HMAC 主密钥或 RSA 私钥分发给它们。
受控本地实验中,选择 JWT,打开 JWT 工具(JWT Tools),算法选 HMAC(HS256),输入一次性共享密钥,再按 Broker 要求设置 claims。本实验把 username 与 MQTT Username 绑定,并检查 iss=mqttable-auth-lab、aud=mqttable-auth-demo。

HS256 使用共享密钥签名。默认的 username、iat、exp 行可以修改,不是所有 Broker 通用的认证策略。
点击 生成并填入(Generate & Fill),把 Token 填入密码草稿,再看 预览(Preview)。签名密钥不是 Token。Broker 先使用对应密钥验证签名,再独立检查 claims。
RS256 的本地签名端需要 PEM 私钥,Broker 需要匹配的公钥。不要把私钥上传到 Broker 的 JWT 公钥验证字段。私钥不能进入截图、共享日志或文章示例文件。

这张配置图拍摄于录入私钥之前。认证结果另外实测,空密钥字段不是已经签名或认证成功的证明。
本地实验生成 RSA 密钥对,只把公钥配置到 EMQX,再用私钥签发客户端 Token。HS256、RS256 都进行连接验证,但不把它写成外部身份提供方或 JWKS 接入验收。
左右滚动表格,查看其余列。
| claim | 要检查的问题 |
|---|---|
| exp | 按验证方的时钟,Token 是否已经过期? |
| nbf | 是否还没到允许使用的时间? |
| iat | 什么时候签发,Broker 是否检查签发时间策略? |
| iss | 签发方是否与配置完全一致? |
| aud | Token 是否签发给当前接受它的服务? |
| sub | 代表哪个主体,如何映射到权限? |
不是每个 Broker 都要求所有 claim。字段类型、时钟容差也要按运营方策略配置。本实验的 username 是额外检查,不代替标准的 sub。RFC 7519定义了这些注册 claim。
JWT Tools 的**预览(Preview)**解码当前草稿,方便阅读 header 和 claims。在 JWT 调试器中打开提供单独的本地检查流程。在本次验证的源码中,调试器验签支持 HS256、HS384、HS512,不支持 RS256。已实际验证 HS256 签名匹配和返回原草稿;RS256 的接受结果来自 Broker,不来自这个调试器。Base64url 解码不等于验证签名;签名正确也可能因为签发方、受众或过期策略不匹配而失败。这里展示的签名 JWS payload 可读,并没有加密。
从 Debugger 返回经过检查或编辑的 Token,不会保存 Client,也不会连接 Broker。这个跳转不会自动取出隐藏的已保存凭据,也不会把签名密钥传给 Debugger。要针对返回后的密码草稿重新测试。Mqttable 的连接路径不会获取、自动续期或刷新 Token。

这是本实验观察到的拒绝。单独一个通用认证错误码,不能证明失败原因一定是过期,而不是签名或其他 claim 不匹配。
Token 过期后是否断开已经在线的客户端,由 Broker 策略决定。它不同于 CONNECT 时拒绝过期 Token,也不是客户端选了 JWT 就自动具备的行为。
SCRAM 使用用户名和密码构造挑战响应。Mqttable 的 SCRAM 模式不把密码放进普通 CONNECT Password 字段。这避免把原始密码作为基础 MQTT 凭据传输,但不会给业务消息加密,也不取消对受保护网络路径的要求。
- 确认 Broker 的精确方法,MQTT 版本选 5.0。
- 选择与它匹配的 SCRAM-SHA-256 或 SCRAM-SHA-512。
- 同时提供用户名和密码;密码可以直接输入,也可以来自有效的 SecretRef。
- 测试当前表单,再连接已保存的 Client,独立验证消息权限。

密码用于本地计算 SCRAM proof;用户名和密码都必须提供。

只修改 Client 不会改变 Broker。本实验先切换隔离 Broker 的认证配置,再验证这个变体。
SCRAM 的 client-first 发送初始身份与 nonce 数据;server-first 提供服务端 nonce、salt 和迭代次数;client-final 证明客户端掌握密码派生材料;server-final 携带服务端签名。具体计算由 SCRAM 机制定义,MQTT 负责承载这些消息。机制依据见 RFC 5802和 SCRAM-SHA-256 的 RFC 7677。
这是协议示意图。初始认证以成功的 CONNACK 完成;AUTH 0x18 表示仍需交换认证数据。
Authentication Method 和 Authentication Data 是属性,不是 AUTH payload;AUTH 没有 payload。方法名必须与 CONNECT 保持一致。AUTH 0x00 表示认证成功,0x18 表示继续认证,0x19 是客户端在连接建立后发起重新认证。初始连接认证和重新认证是两条不同的流程。
协议允许重新认证,不代表本文已验证 Mqttable 提供自动或手动重新认证控件。这个下拉也没有宣称包含 SCRAM-SHA-256-PLUS 等 channel binding 变体。
Mqttable 提供 SCRAM-SHA-1。仅在明确的旧系统要求下使用;新部署有选择时,优先采用双方支持的 SHA-256 或 SHA-512。不能因为较强方法连接失败,就自行降级。

本实验 Broker 不支持 SHA-1。这不是 SHA-1 成功交换,也不能据此认定客户端实现出错。
编辑已保存的客户端,不等于重新录入密码。明文密码模式中的 Password saved · Kept unless replaced 表示保留已有密码,但不显示它的原文。

已保存密码不是空凭据。界面有意不回显原文。
- 保留: 不修改已有凭据。进入替换模式后,输入留空且未选 Clear on save,也保留原值。
- 替换: 点击 Replace,输入新密码或 Token,在 Client 断开时测试当前表单,再保存。测试成功本身不会保存替换值。
- 清除: 明确选择 Clear on save 后保存。如果当前方式要求凭据,表单可能拒绝这个空状态;保存被拒绝不等于凭据已经清除。
- Saved SecretRef: 选择正确引用。这是另一种凭据来源,不是额外的一层认证。切回 Raw password 时,不能假设引用中的原文会自动复制或显示。
- None: 当前连接不再使用密码,不等于删除已保存的 Secret 或引用。
验证变更前先断开 Client。已经在线时,Mqttable 显示真实运行状态,并提示当前编辑未验证;草稿不能证明现有连接已使用新设置。完整保存与连接流程见 Broker 与连接。
Broker 测试连接使用端点及当前传输、TLS 设置,不发送 New Client 的用户名和密码。这个探测被拒绝认证,本身不能证明 TLS 证书错误。
Client 测试连接不保存配置,测试完整当前表单。它可以使用尚未保存的密码或 Token、保留的旧凭据、SecretRef,以及 SCRAM 选择。成功后临时连接立即断开,不是持久的 Connected Client,也不验证消息权限。
测试使用真实 Client ID 和会话设置。同身份的其他客户端可能被断开,它的 Will 可能被触发,会话状态也可能受影响。Mqttable 会阻止已知本地在线身份的测试,但不能排除未知外部客户端。本实验使用专门 Client ID,不配置 Will,不使用有价值的持久会话。
对每个认证成功的方法,再做一次消息验证:
- 保存并连接 Client,确认 Connected,并在可见时检查成功的 CONNACK。
- 订阅明确允许的
mqttable/auth/lab/<method>,QoS 1,关闭 No Local。 - 等待成功 SUBACK,再发布。
- 向同一 Topic 发布少量合成 JSON,QoS 1,Retain 关闭。
- 找到 Topic、payload 相同的发送与接收 PUBLISH。单独看到 PUBACK,不能证明订阅者收到消息。

消息闭环比 CONNECT 多证明了一步,但不能证明 TLS、其他 Topic 的权限或生产可用性。
本实验的 Mosquitto 密码 Listener 拒绝另一个 Topic:mqttable/auth/denied。保持同一组有效凭据,订阅这个 Topic,在这版 Mosquitto 配置中会得到成功 SUBACK;但以 QoS 1 发布,收到的是 PUBACK 0x87(Not authorized)。订阅回执不是发布权限的授权,读取 ACL 也仍可过滤投递。截图展示的是已经通过认证、但发布操作没有授权的结果。

保留已接受的身份,检查它的 ACL。这个失败不应先靠替换正确密码处理。
左右滚动表格,查看其余列。
| 观察到的结果 | 下一步检查 |
|---|---|
| 表单提示 JWT Token 或 SCRAM 凭据必填 | 凭据来源、SecretRef 是否有效、用户名与密码、MQTT 版本;此时还没进行网络认证 |
| CONNACK 之前 TLS 握手失败 | Host、Listener 协议、CA 信任、证书名称和客户端证书要求 |
| MQTT 5 CONNACK 0x86,Bad User Name or Password | 账号、密码或 Token、JWT 签名与 claims、认证器状态及 Listener 策略 |
| CONNACK 0x87,Not authorized | 连接权限和认证链;结果本身不能定位到某条 ACL |
| CONNACK 0x8C,Bad authentication method | 精确增强认证方法和 Broker 支持范围 |
| Connected 之后 SUBACK 或 PUBACK 拒绝操作 | Topic、操作类型、QoS/Retain 限制和已认证主体的授权规则 |

本负例只改变了一项凭据。其他部署出现通用拒绝时,仍需要自己的证据。
不要假定所有 Broker 都返回相同的详细原因。MQTT 标准允许在多条路径拒绝或关闭连接,运营方也可能有意隐藏凭据失败的细节。记录版本、Listener、方法、报文方向和实际结果,不把密码、Token 或私钥复制到工单。认证方式指南提供更短的配置参考。
不必须。放进 CONNECT Password 的 Token 是基础凭据传递,Broker 支持时也可以用于 MQTT 3.1.1。增强认证机制可以定义 Token 交换,但 Mqttable 这里的 JWT 模式不是那种流程。
不能。不把原始密码放进 CONNECT Password,不等于保护整条连接。使用正确验证身份的 TLS 或经过明确评估的受保护传输,也不要把本文 loopback Listener 直接开放到公网。
Broker 接受了连接身份,仍可能拒绝 Topic 操作。检查 SUBACK、PUBACK 和 ACL,再在允许的 Topic 上复跑发布与订阅流程。
解码只显示结构,接受与否仍取决于签名和 claims 策略。这个连接路径不自动获取或刷新 Token。按签发方授权流程取得新 Token,替换后重新测试草稿。
不可以。实验材料都是一次性的。按运营方策略创建凭据,把签名密钥留在正确的签发边界,泄露后及时轮换。先确定 Broker 要求的机制,再证明连接成功,最后证明你实际需要的 Topic 操作。