Mosquitto 与 MQTT v3.1:用户名密码认证的引入、演进与源码级实现解析
物联网消息队列后端【免费下载链接】mosquittoEclipse 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 dpMQ 消息分发协议的遗留命名版本号Protocol Level为3v3.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 Flagbit 6 为密码标志Password Flag。send__connect()中的对应逻辑为byte (uint8_t)((clean_session0x1)1); if(will){ byte byte | (uint8_t)(((mosq-will-msg.qos0x3)3) | ((will0x1)2)); ... } if(username){ byte byte | 0x17; /* 设置 Username Flag */ } if(mosq-password){ byte byte | 0x16; /* 设置 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_version0x7F) ! PROTOCOL_VERSION_v31){ /* v3.1 必须携带版本号 3可带 0x80 私用标志位故用 0x7F 屏蔽 */ ... } if((protocol_version0x7F) 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.henum 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/qAWXcmEN/mjDNgdUwcxRAveqNMs2xUVQYA sub_client:$6$Uqg0/32F0g2Fhn$fBPSkq/rfNyEQ/TkEjRgwGTTVBpvNhKSyGShovH9KHewsvJ731tD5Zx26IHhR5RYCICt0L9qBW0/KK31UkCliw pub_client:$6$vxQ89y7WrsnL2yn$fSPMmEZn9TSrC8s/jaPmxJ9NijWpkP2e7bMJLz78JXR1vW2x8T3FZ23byJA6xs5MtLeOybAHwcUv0OCl40rA5.2mosquitto_passwd生成与维护密码文件推荐使用官方工具生成上述文件其用法见 apps/mosquitto_passwd/mosquitto_passwd.cUsage: 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/pwfile5.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_filemosquitto_passwd生成哈希文件策略层按用户名控制 Broker/主题访问allow_anonymousacl_file按用户段授权返回码认证失败拒绝码CONNACK_REFUSED_BAD_USERNAME_PASSWORD4无论你使用的是 v3.1、v3.1.1 还是 v5 客户端理解这条从 CONNECT 报文构造、密码文件校验到 ACL 授权的主链路都能帮你更准确地排查连接被拒、权限不足等实际问题。赞分享物联网消息队列后端【免费下载链接】mosquittoEclipse Mosquitto - An open source MQTT broker项目地址https://gitcode.com/gh_mirrors/mosquit/mosquitto点击查看免费下载相关推荐MQTT v3.1 认证演进从 CONNECT 报文用户名/密码到 Mosquitto 的访问控制实战MQTT v3.1 认证演进从 CONNECT 报文用户名/密码到 Mosquitto 的访问控制实战 MQTT v3.1 是 MQTT 协议发展史上的关键里物联网消息队列后端网络/通信Mosquitto 0.10 发布解读MQTT v3.1 认证与 ACL 访问控制的实现与演进Mosquitto 0.10 发布解读MQTT v3.1 认证与 ACL 访问控制的实现与演进 导读 Eclipse Mosquitto 0.10 于 201后端消息队列消息路由FastStream MQTT 安全配置实战指南TLS 加密与 SASL 用户名密码认证FastStream MQTT 安全配置实战指南TLS 加密与 SASL 用户名密码认证 FastStream 的 MQTTBroker 与 Kafka、Ra后端消息队列微服务上一篇青龙面板全平台自动签到工具告别繁琐操作每天节省30分钟下一篇OBS-VST插件终极指南在OBS中免费使用专业VST音频插件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考