news 2026/9/27 21:38:35

Mosquitto 与 MQTT v3.1:用户名密码认证的引入、演进与源码级实现解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Mosquitto 与 MQTT v3.1:用户名密码认证的引入、演进与源码级实现解析
  • 物联网
  • 消息队列
  • 后端

【免费下载链接】mosquitto

Eclipse Mosquitto - An open source MQTT broker

项目地址:https://gitcode.com/gh_mirrors/mosquit/mosquitto
点击查看免费下载

本文基于 Eclipse Mosquitto 仓库中发布于 2010 年的官方博文 mqtt-v3-1.md,梳理 MQTT v3.1 规范相对 v3.0 的核心变化——在 CONNECT 报文中加入可选的用户名与密码字段,并结合当前仓库源码展示 Mosquitto 如何实现、校验与配置这一认证能力。读完本文,你将掌握 v3.1/v3.1.1 在 Mosquitto 客户端与 Broker 两侧的完整报文构造与解析链路,以及如何通过password_file、allow_anonymous与 ACL 文件实现按用户名控制 Broker 与主题访问。

一、背景:MQTT v3.1 的诞生与关键变化

MQTT v3 规范随后被更新为 v3.1。这次更新的最显著变化,是在 CONNECT(连接)命令中加入了发送**用户名(username)和密码(password)**的可选项。也就是说,从 v3.1 开始,MQTT 协议在应用层原生支持了最基本的身份认证手段,客户端可以在建立连接时向 Broker 声明自己的身份。

这一点在当时的背景尤为重要:v3.1 规范本身比原始版本"可读性更强、更清晰",同时 Mosquitto 官方在 2010 年发布该博文时明确表示,Mosquitto 将在未来版本中支持 v3.1 规范,并支持按用户名控制 Broker 与主题访问权限。若用户当时急需该能力,可暂用 IBM 的 RSMB 代理做测试(其包内同样包含 MQTT 客户端库、简易发布客户端与订阅客户端,但需自行核对许可证条款)。

从今天的仓库视角回看,这一承诺已完整落地:当前 Mosquitto 同时支持 MQTT v3.1、v3.1.1 与 v5 三种协议版本,用户名密码认证、ACL 主题访问控制均已成为内置安全模块的核心能力。

二、协议层面的标识:v3.1 与 v3.1.1 的字节级差异

要理解 Mosquitto 如何区分协议版本,可以先看协议常量定义文件 include/mosquitto/mqtt_protocol.h:

#define PROTOCOL_NAME_v31 "MQIsdp" /* v3.1 使用的协议名 */ #define PROTOCOL_VERSION_v31 3 #define PROTOCOL_NAME "MQTT" /* v3.1.1 与 v5 使用的协议名 */ #define PROTOCOL_VERSION_v311 4 #define PROTOCOL_VERSION_v5 5

这里揭示了两个关键事实:

  • v3.1 的 CONNECT 报文中,协议名(Protocol Name)字段写的是"MQIsdp"(即 MQ Is dp,MQ 消息分发协议的遗留命名),版本号(Protocol Level)为3;
  • v3.1.1 与 v5的协议名统一为"MQTT",版本号分别为4与5。

对应地,客户端库选项在 include/mosquitto/defs.h 中定义为:

#define MQTT_PROTOCOL_V31 3 #define MQTT_PROTOCOL_V311 4

三、客户端侧实现:CONNECT 报文的构造与用户名密码写入

3.1 报文构造主流程

客户端发送 CONNECT 的核心实现在 lib/send_connect.c 的send__connect()函数中。它根据mosq->protocol选择版本,并计算不同的可变报头长度:

}else if(mosq->protocol == mosq_p_mqtt311){ version = MQTT_PROTOCOL_V311; headerlen = 10; }else if(mosq->protocol == mosq_p_mqtt31){ version = MQTT_PROTOCOL_V31; headerlen = 12; /* v3.1 的协议名字符串比 v3.1.1 长 2 字节 */ }

紧接着写入协议名与版本号:

if(version == MQTT_PROTOCOL_V31){ packet__write_string(packet, PROTOCOL_NAME_v31, (uint16_t)strlen(PROTOCOL_NAME_v31)); }else{ packet__write_string(packet, PROTOCOL_NAME, (uint16_t)strlen(PROTOCOL_NAME)); } packet__write_byte(packet, version);

3.2 连接标志位:用户名/密码位如何被置位

v3.1/v3.1.1 的 CONNECT 报文第 8 字节是"连接标志(Connect Flags)",其中 bit 7 为用户名标志(Username Flag),bit 6 为密码标志(Password Flag)。send__connect()中的对应逻辑为:

byte = (uint8_t)((clean_session&0x1)<<1); if(will){ byte = byte | (uint8_t)(((mosq->will->msg.qos&0x3)<<3) | ((will&0x1)<<2)); ... } if(username){ byte = byte | 0x1<<7; /* 设置 Username Flag */ } if(mosq->password){ byte = byte | 0x1<<6; /* 设置 Password Flag */ } packet__write_byte(packet, byte);

可见用户名与密码标志是相互独立置位的:客户端可以只带用户名(典型场景),也可以用户名密码同时携带;而密码必须依赖用户名。

3.3 一个容易踩的坑:只有密码没有用户名

send__connect()在组装报文前做了前置校验:

if(mosq->protocol == mosq_p_mqtt31 || mosq->protocol == mosq_p_mqtt311){ if(password != NULL && username == NULL){ return MOSQ_ERR_INVAL; /* 拒绝构造:v3.1/v3.1.1 不允许只有密码没有用户名 */ } }

也就是说,在 v3.1 与 v3.1.1 中,如果只设置了密码而未设置用户名,mosquitto_connect()系列调用会直接返回MOSQ_ERR_INVAL,因为按规范密码标志位只有在用户名标志位为 1 时才有意义。

3.4 负载(Payload)的写入顺序

用户名与密码写入 CONNECT 报文体(payload)的末尾,紧随客户端 ID 与遗嘱消息(Will)之后:

/* Payload */ if(clientid){ packet__write_string(packet, clientid, (uint16_t)strlen(clientid)); }else{ packet__write_uint16(packet, 0); } if(will){ packet__write_string(packet, mosq->will->msg.topic, ...); packet__write_string(packet, (const char *)mosq->will->msg.payload, ...); } if(username){ packet__write_string(packet, username, (uint16_t)strlen(username)); } if(password){ packet__write_string(packet, password, (uint16_t)strlen(password)); }

负载中每个字符串都遵循"2 字节长度 + 内容"的编码方式(packet__write_string),这也正是 v3.1 规范对 UTF-8 字符串字段的统一要求。

四、Broker 侧实现:CONNECT 报文的解析与版本校验

4.1 版本号检查与协议分发

Broker 端解析 CONNECT 的逻辑位于 src/handle_connect.c。它对协议版本号做了严格校验:

if((protocol_version&0x7F) != PROTOCOL_VERSION_v31){ /* v3.1 必须携带版本号 3(可带 0x80 私用标志位,故用 &0x7F 屏蔽) */ ... } if((protocol_version&0x7F) == PROTOCOL_VERSION_v311){ ... }

从这段代码可以推断:Broker 允许 v3.1 的版本字节带有最高位(bit 7)的附加标志(这一机制后来被桥接模式的try_private私用扩展使用,见 lib/send_connect.c 中version |= 0x80的桥接分支),因此校验时统一用&0x7F取出低 7 位再比较。

4.2 认证通过后的接入流程

当用户名密码通过校验后,Broker 进入connect__on_authorised()完成后续接入:

  • 检查同名客户端是否已在线,若在线则执行"会话接管(session taken over)"逻辑,向旧连接发送 DISCONNECT 并断开;
  • 处理clean_session/clean_start与持久会话的订阅、Inflight 消息迁移;
  • 记录连接日志,例如New client connected from %s:%d as %s (p%d, c%d, k%d, u'%s')(u'...'即登录用户名,见 src/handle_connect.c);
  • 按keepalive、max_qos等策略初始化连接后,发送CONNACK_ACCEPTED。

4.3 认证失败时的 CONNACK 返回码

v3.1/v3.1.1 共用的 CONNACK 返回码定义在 include/mosquitto/mqtt_protocol.h:

enum mqtt311_connack_codes { CONNACK_ACCEPTED = 0, CONNACK_REFUSED_PROTOCOL_VERSION = 1, CONNACK_REFUSED_IDENTIFIER_REJECTED = 2, CONNACK_REFUSED_SERVER_UNAVAILABLE = 3, CONNACK_REFUSED_BAD_USERNAME_PASSWORD = 4, /* 用户名或密码错误 */ CONNACK_REFUSED_NOT_AUTHORIZED = 5, };

其中CONNACK_REFUSED_BAD_USERNAME_PASSWORD(值为 4)正是为 v3.1 引入用户名密码认证而对应的拒绝码——客户端携带错误的用户名/密码时,Broker 会以此返回码拒绝连接。

五、让 v3.1 认证真正生效:密码文件与访问控制配置

v3.1 引入的"用户名控制 Broker 与主题访问"能力,在 Mosquitto 中落地为内置安全模块(builtin-security)。相关配置项的解析在 src/conf.c 中,安全数据的加载在 src/security_default.c 中完成。

5.1password_file:指定用户名密码文件

# mosquitto.conf password_file /etc/mosquitto/pwfile

配置解析位于 src/conf.c:该选项把文件路径存入security_options->password_file。Broker 启动时,mosquitto_security_init_default()会调用unpwd__file_parse()解析该文件,并注册MOSQ_EVT_BASIC_AUTH回调(src/security_default.c)。若启用了per_listener_settings true,每个 listener 可拥有各自的password_file,且必须将其置于其他安全配置项之前。

密码文件的格式为每行一个条目:

username:hashed-password

仓库根目录的 pwfile.example 给出了真实示例(密码以$6$开头的 SHA-512 加盐哈希存储):

roger:$6$clQ4Ocu312S0qWgl$Cv2wUxgEN73c6C6jlBkswqR4AkHsvDLWvtEXZZ8NpsBLgP1WAo/qA+WXcmEN/mjDNgdUwcxRAveqNMs2xUVQYA== sub_client:$6$U+qg0/32F0g2Fh+n$fBPSkq/rfNyEQ/TkEjRgwGTTVBpvNhKSyGShovH9KHewsvJ731tD5Zx26IHhR5RYCICt0L9qBW0/KK31UkCliw== pub_client:$6$vxQ89y+7WrsnL2yn$fSPMmEZn9TSrC8s/jaPmxJ9NijWpkP2e7bMJLz78JXR1vW2x8+T3FZ23byJA6xs5Mt+LeOybAHwcUv0OCl40rA==

5.2mosquitto_passwd:生成与维护密码文件

推荐使用官方工具生成上述文件,其用法(见 apps/mosquitto_passwd/mosquitto_passwd.c):

Usage: mosquitto_passwd [-H argon2 | -H sha512-pbkdf2] [-c | -D] passwordfile username mosquitto_passwd [-H argon2 | -H sha512-pbkdf2] [-c] -b passwordfile username password mosquitto_passwd -U passwordfile

常用操作示例:

# 新建密码文件并添加用户(-c 表示创建/覆盖文件) mosquitto_passwd -c /etc/mosquitto/pwfile roger # 批量模式:直接以命令行参数指定密码(适合脚本) mosquitto_passwd -b /etc/mosquitto/pwfile sub_client sub_password # 删除用户(-D) mosquitto_passwd -D /etc/mosquitto/pwfile roger # 将旧格式密码文件升级为新的哈希格式(-U) mosquitto_passwd -U /etc/mosquitto/pwfile

5.3allow_anonymous:是否放行匿名连接

设置password_file后,还需注意匿名策略。在 src/conf.c 中有如下推断逻辑:一旦配置了认证/访问控制类选项(如password_file或acl_file),且未显式设置allow_anonymous,则该值被自动置为false——即"配了密码文件就默认禁止匿名登录"。

显式配置方式:

allow_anonymous false

在 src/security_default.c 的认证流程中,当allow_anonymous为false且客户端未提供用户名时,匿名连接会被直接拒绝;反之若allow_anonymous true,匿名客户端仍可连接,但只能访问 ACL 允许的主题。

5.4acl_file:按用户名控制主题访问

"按用户名控制主题访问"由 ACL 文件实现:

# mosquitto.conf acl_file /etc/mosquitto/aclfile

仓库根目录的 aclfile.example 提供了模板,典型结构为:

# 授予特定用户主题访问权限 user roger topic read/write sensor/# # roger 可读写 sensor/# 主题 topic read $SYS/# # roger 可读 $SYS/# 系统主题 # 对未匹配任何 user 段的客户端生效 topic read $SYS/#

ACL 文件在 Broker 启动时由aclfile__parse()解析,并注册MOSQ_EVT_ACL_CHECK回调(src/security_default.c)。每次 PUBLISH 与 SUBSCRIBE 都会经过该回调,按"客户端用户名 → 匹配的 user 段 → topic 模式"的顺序做权限裁决,从而实现真正意义上的"按用户名控制主题访问"。

5.5 从命令行指定协议版本

客户端侧可通过-V参数选择协议版本(client/client_shared.c):

# 使用 MQTT v3.1(协议名 MQIsdp,版本号 3) mosquitto_pub -V mqttv31 -u roger -P password -t sensor/temp -m 23.5 # 使用 MQTT v3.1.1(默认) mosquitto_sub -V mqttv311 -u roger -P password -t sensor/#

-u/-P分别指定用户名与密码;当仅指定-u时,客户端只发送用户名而不发送密码(连接标志只置 Username Flag)。工具mosquitto_ctrl同样支持-V切换协议(见 apps/mosquitto_ctrl/options.c)。

六、从 v3.1 到 v3.1.1 的演进脉络

v3.1 引入用户名密码字段后,MQTT 协议认证能力得以标准化,但也留下了一些歧义(如客户端 ID 为空时的行为、遗嘱消息标志的位序等)。随后发布的 v3.1.1(即 OASIS 标准 MQTT 3.1.1)在保留用户名/密码字段设计的同时,将协议名统一为"MQTT"、版本号升为4,并大幅收紧规范措辞。

Mosquitto 对两者的支持是完全并存的:

  • 客户端库默认使用MQTT_PROTOCOL_V311(见 include/mosquitto/libmosquitto_options.h 中MOSQ_OPT_PROTOCOL_VERSION的说明);
  • Broker 端对 v3.1 与 v3.1.1 的 CONNECT 均接受,且按PROTOCOL_VERSION_v31/PROTOCOL_VERSION_v311分别处理(src/handle_connect.c);
  • 用户名密码的写入、置位与校验逻辑在 lib/send_connect.c 中对两个版本完全共用(if(mosq->protocol == mosq_p_mqtt31 || mosq->protocol == mosq_p_mqtt311))。

可以说,v3.1 开创的用户名密码认证,是 MQTT 安全体系的地基:它在协议层面定义了"客户端如何自报身份",而 Mosquitto 则在此基础上叠加了密码文件校验、匿名策略、ACL 主题授权乃至 TLS/PSK 等多种安全机制,最终形成今天完整的接入控制链路。

七、小结

从 2010 年这篇博文到今天,MQTT v3.1 引入的用户名密码认证已完成从"规范选项"到"成熟实现"的演进:

层级v3.1 引入的能力Mosquitto 中的落地
协议层CONNECT 报文携带可选用户名/密码客户端send__connect()构造,Brokerhandle_connect.c解析
协议标识协议名MQIsdp、版本号 3include/mosquitto/mqtt_protocol.h 中PROTOCOL_NAME_v31
认证层用户名密码校验password_file+mosquitto_passwd生成哈希文件
策略层按用户名控制 Broker/主题访问allow_anonymous+acl_file按用户段授权
返回码认证失败拒绝码CONNACK_REFUSED_BAD_USERNAME_PASSWORD(4)

无论你使用的是 v3.1、v3.1.1 还是 v5 客户端,理解这条从 CONNECT 报文构造、密码文件校验到 ACL 授权的主链路,都能帮你更准确地排查连接被拒、权限不足等实际问题。

  • 物联网
  • 消息队列
  • 后端

【免费下载链接】mosquitto

Eclipse Mosquitto - An open source MQTT broker

项目地址:https://gitcode.com/gh_mirrors/mosquit/mosquitto
点击查看免费下载
上一篇:青龙面板全平台自动签到工具:告别繁琐操作,每天节省30分钟
下一篇:OBS-VST插件终极指南:在OBS中免费使用专业VST音频插件

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 21:38:12

老设备还能越狱:palera1n 在 A8–A11 上的 checkm8 越狱完整实操

老设备还能越狱&#xff1a;palera1n 在 A8–A11 上的 checkm8 越狱完整实操 【免费下载链接】palera1n Jailbreak for A8 through A11, T2 devices, on iOS/iPadOS/tvOS 15.0, bridgeOS 5.0 and higher. 项目地址: https://gitcode.com/GitHub_Trending/pa/palera1n pa…

作者头像 李华
网站建设 2026/9/27 21:38:03

wp-calypso PluginIcon 组件深度解析:插件图标渲染的完整实战指南

前端CMS 【免费下载链接】wp-calypso The JavaScript and API powered WordPress.com 项目地址&#xff1a; https://gitcode.com/gh_mirrors/wp/wp-calypso 点击查看 免费下载 本指南围绕 WordPress.com 的 JavaScript 客户端项目 wp-calypso 中的 PluginIcon 组件展开&#…

作者头像 李华
网站建设 2026/9/27 21:36:57

Cursor 终端乱码解决:TaoToken 配置 settings.json 与 chcp 65001 实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华