尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

EMQX ACL权限管控实战:MQTT主题通配符与授权配置指南

EMQX ACL权限管控实战:MQTT主题通配符与授权配置指南 1. 从能连上到管得住ACL在EMQX里到底管什么很多人第一次把EMQX跑起来、用MQTTX连上、看到消息收发正常就觉得这套消息系统已经通了。我刚开始接触MQTT那会儿也是这个心态直到有个朋友问我一句你那broker是不是谁都能往任意主题发消息我才意识到问题的严重性——默认配置下的EMQX只要客户端能连上就能订阅任何主题、往任何主题发布消息。这在局域网自己玩玩没问题一旦放到真实项目里等于把整个消息总线的大门敞开。ACLAccess Control List访问控制列表解决的就是连上之后能干什么这个问题。它和认证Authentication是两个层面的事认证管的是你是谁、你能不能进来ACL管的是进来之后你能发布到哪些主题、能订阅哪些主题。这个区分特别关键我见过不少新手把两者混为一谈结果账号密码设了一堆权限还是乱的。打个比方认证像是小区大门的门禁卡刷卡才能进小区ACL则像是每栋楼、每个单元甚至每户的门禁进了小区不代表你能进别人家。EMQX里这两套机制是分开配置的认证走的是认证插件比如用户名密码、JWT等ACL走的是授权Authorization配置。默认情况下EMQX的授权是允许所有也就是说认证过了之后就畅通无阻所以我们必须主动去配ACL。这篇笔记我打算把我在实际项目里折腾EMQX ACL的经验完整梳理一遍它有哪些规则来源、规则怎么匹配、主题通配符怎么处理、常见坑在哪里。适合已经跑通EMQX基本收发、准备给系统加上权限管控的开发者。如果你还在怎么安装EMQX怎么用MQTTX连这个阶段建议先把基础流程走一遍再回来看不然容易一头雾水。2. 权限模型与规则来源先把谁按什么顺序说了算搞明白2.1 授权检查发生在哪个环节先建立个时间线概念。一个MQTT客户端从连上到收发消息EMQX内部大致经过这么几步TCP连接建立、CONNECT报文认证、然后是每次PUBLISH和SUBSCRIBE时的授权检查。ACL检查是逐条消息触发的不是连接时一次性判定。这一点很重要意味着你改一条ACL规则已经在线的客户端下一次发消息就会用到新规则不需要全部断开重连当然客户端重连最保险下面会讲为什么。发布PUBLISH和订阅SUBSCRIBE这两个动作都会触发授权检查但注意EMQX默认对订阅的授权检查是检查订阅的主题过滤器本身。换句话说你请求订阅sensors/#EMQX检查的是sensors/#这个过滤器是否被允许而不是检查它将来会匹配到的每条具体消息。这个设计直接影响你怎么写规则——如果你允许某个客户端订阅sensors/#那它就能收到所有sensors/开头的消息包括你不希望它看到的那部分。这也是为什么后面讲通配符时要特别小心。2.2 三种授权数据来源EMQX的授权规则可以来自三个地方按优先级从高到低大致是来源说明适用场景内置数据库内置 ACL通过Dashboard或配置文件写的静态规则中小规模、规则数量可控文件acl.conf早期版本的经典方式写到配置文件里简单固定规则、快速验证外部数据源HTTP/MySQL/PostgreSQL/Redis/MongoDB规则存在外部系统EMQX实时查询大规模、规则动态变化、与业务系统联动我个人的使用经验是先用内置ACL把权限跑通、验证逻辑对不对再考虑要不要挪到外部数据源。很多小项目其实内置ACL就够了没必要上Redis。但如果你有成千上万个设备、每个设备主题都不一样那外部数据源几乎是必然选择因为内置规则你手写不过来。2.3 授权检查的匹配顺序这是最容易踩坑的地方EMQX的授权检查遵循一套顺序规则理解这个顺序能省你无数排查时间。核心原则是规则按顺序逐条匹配一旦匹配到就立即返回结果allow或deny不再往后看。所有规则都没匹配到时取决于默认策略no_match默认通常是allow生产环境强烈建议改成deny。在某些规则类型下deny会优先于allow生效具体要看规则的组织方式。注意很多人的ACL配了半天不生效最后发现是顺序问题——一条宽泛的allow规则写在前面把后面的deny全盖住了。规则顺序不是随便排的越具体的规则越要往前放或者善用deny优先。我踩过最典型的一个坑给一个设备写了allow它发布到device/001/#又写了deny它发布到device/001/control心想大范围允许、小范围禁止。结果deny那条死活不生效消息照样发出去。原因就是allow写在前面先匹配到了。解决办法是把deny规则提到allow之前或者干脆用外部数据源的优先级机制。2.4 授权来源的选择依据到底用哪种来源我总结了一个简单的判断规则固定、数量在几十条以内、不需要运行时改内置ACL足够。规则跟业务账号绑定、需要动态增删上HTTP或数据库。想验证一个是不是权限问题临时用acl.conf或Dashboard加一条deny最快。这个判断不是绝对的但能帮你快速起手。下面重点讲内置ACL和配置细节因为这是最通用、最不影响别人系统的玩法。3. 主题通配符与匹配规则ACL里写主题比你想的要讲究3.1 MQTT主题的两个通配符在写ACL之前必须先把MQTT主题的通配符搞透否则规则写得再多也是错的。MQTT主题里有两个通配符单层通配符匹配一层主题。比如sensor//temp能匹配sensor/room1/temp但匹配不了sensor/room1/floor1/temp。#多层通配符匹配零层或多层只能出现在主题末尾且必须是/后的最后一段。比如sensor/#能匹配sensor、sensor/room1、sensor/room1/temp。关键在于发布消息时不能带通配符订阅时才能带通配符。所以你在ACL规则里看到通配符基本都是在描述订阅可以覆盖的范围或者用通配符去描述一类主题。3.2 ACL规则里的主题怎么写才安全ACL规则一般由几部分组成主体谁、动作发布/订阅、主题对哪个主题、效果允许/拒绝。举几个我实际用过的例子allowusernamedev001publishdevice/dev001/updenyusernamedev001publishdevice/dev001/controlallowusernamedev001subscribedevice/dev001/down/#这里的第一条和第二条都是精确主题没有通配符这种最安全因为范围完全可控。第三条用了#表示dev001能订阅它自己下行的所有子主题。这种写法要保证前缀里的设备ID是唯一的否则会出现dev001能匹配到dev0011这种滑稽事——虽然MQTT主题是分层匹配device/dev001/down/#不会匹配device/dev0011/down/x但在做前缀拼接的时候很容易出错比如用字符串拼device/ deviceId如果deviceId本身包含/主题层级就乱了。提示设备ID、租户ID这类变量在拼接成主题时一定要做字符校验禁止包含/、、#这三个字符。我见过因为设备序列号里带#导致主题注入、权限绕过的案例虽然场景特殊但教训是真的。3.3 用占位符让规则复用内置ACL支持在主题里用${username}、${clientid}这类占位符这是省事的关键。比如allow ${username} publish devices/${username}/data allow ${username} subscribe devices/${username}/cmd/#这样一条规则就能覆盖所有用户每个用户只能操作自己命名空间下的主题。但前提是用户名本身是干净的、不含特殊字符。如果你的用户名是邮箱或者带点号的字符串虽然一般不影响主题但让主题看起来很奇怪建议用映射或统一的ID。占位符还能用${clientid}、${ip}等具体支持哪些要看EMQX版本不同大版本之间会有差异。我建议用之前先查一下对应版本的文档别照着旧博客抄容易不生效。3.4 一个容易忽略的点订阅检查的是过滤器前面提过一次这里展开说。当客户端发SUBSCRIBE请求订阅a/#时EMQX检查的是字符串a/#是否被允许而不是展开后逐个检查。这意味着规则里写allow subscribe a/b客户端订阅a/#很可能就被拒了因为a/#不等于a/b。反过来规则里写allow subscribe a/#客户端订阅a/#通过那它就能收到a/下所有消息你没法在ACL层面再细分拦截——除非用deny规则配合顺序。所以设计主题空间时我强烈建议提前规划好层级让订阅权限和主题范围是一一对应的别指望订阅一批再筛一批。这是主题设计阶段就要考虑的事等规则写完再改代价很大。4. 动手配置内置ACL从零到可用的完整流程4.1 准备工作确认版本和入口先说明不同EMQX版本4.x和5.x的配置界面和配置文件格式差别不小我这里以5.x的思路为主4.x我会在关键处标注。配置入口有两个Dashboard的访问控制-授权页面以及配置文件emqx.conf5.x或etc/emqx.confetc/acl.conf4.x风格。我习惯两边都看Dashboard适合快速验证配置文件适合版本化管理和批量下发。动手前建议先备份当前配置尤其是有认证插件已经在跑的环境。授权和认证虽然分开但配置错了确实会让人一时连不上有备份能快速回滚。4.2 通过Dashboard添加内置ACL规则以5.x为例大致流程是登录Dashboard进入访问控制→授权。选择授权数据源为内置数据库没启用的话先启用。添加规则依次填主体类型如username、主体值、动作发布/订阅/全部、主题支持通配符和占位符、效果允许/拒绝。调整默认策略no_match生产环境设为deny或ignore别留着allow。保存后规则即时生效。这里有个实操细节Dashboard里的规则是有顺序的界面通常允许拖拽或按添加顺序生效。添加时想清楚deprioritize哪条。我一般按deny在前、allow在后具体在前、宽泛在后的原则排列。4.3 用配置文件批量下发配置文件适合一次性写一整套规则。5.x里授权相关配置大致长这样示意字段名以你实际版本为准authorization { no_match deny deny_action ignore sources [ { type built_in_database enable true } ] }规则本身通过Dashboard或API写入内置库。4.x风格则更直接写在acl.conf里{allow, {username, dev001}, publish, [device/dev001/up]}. {deny, {username, dev001}, publish, [device/dev001/control]}. {allow, {username, dev001}, subscribe, [device/dev001/down/#]}.acl.conf的好处是直观、易读、能进Git。坏处是改完要reload或重启部分版本支持热加载。我早期项目就靠一个acl.conf打天下几十条规则维护得也挺清楚。4.4 默认策略必须改这一条我要单独拎出来说因为它太容易被忽略也太危险。默认策略no_match出厂值是allow意思是所有规则都没匹配上时放行。如果你的规则本来就不全等于留了个大后门。生产环境务必改成deny没匹配到就拒绝最安全推荐。ignore不匹配就走别的授权源或保持中立多源场景才用。改完之后一定要用真实客户端测一遍没有规则覆盖的主题能不能发、能不能订确认拒绝生效了。我有次改了配置没重启成功规则实际没生效测了才发现白紧张一场。4.5 验证用MQTTX做正反用例配置完别急着上线拿MQTTX这种图形客户端做正反用例最直观正向用例用dev001连上往device/dev001/up发消息应当成功订阅device/dev001/down/#应当成功。反向用例用dev001往device/dev002/up发消息应当被拒MQTT协议层面表现为连接不断但该PUBLISH被丢弃或返回相应错误具体表现看协议版本和配置。边界用例用dev001订阅device/#根据你的规则应当被拒验证通配符拦截有效。注意MQTT 3.1.1对PUBLISH被拒的处理比较温柔可能只是被服务端丢弃客户端不一定收到明确错误。所以别只靠客户端提示判断最好去EMQX日志里看授权拒绝记录那里才是真相。4.6 看日志确认拒绝原因排查权限问题时日志是第一现场。EMQX会把授权检查结果记下来被拒时能看到是哪个clientid、哪个用户名、对哪个动作和主题被拒。把日志级别调到合适程度别一直开debug量大复现问题时专看这一段。我排查过的权限问题八成在日志里一眼就能定位剩下两成是规则顺序或占位符拼错。5. 常见问题排查这些坑我基本都替你踩过了5.1 规则写了却不生效这是最高频的问题原因基本跑不出这几类我整理成速查表现象可能原因排查方向规则改了没反应配置未reload/重启或改错了文件确认生效方式看启动日志读了哪个文件deny规则不生效allow规则在前先匹配调整顺序deny提前占位符没替换版本不支持该占位符或用户名取值不符预期查版本文档日志打印实际取值所有主题都被拒默认策略改deny且规则没匹配上补规则检查主体类型是否选对特定clientid规则无效客户端clientid动态变化如随机后缀改用username做主体或固定clientid5.2 认证和授权混淆导致的误判我明明加了权限怎么还能发——先确认你说的加了权限是认证还是授权。常见误区是只在认证里建了用户就以为权限自动受限了。不是的认证只解决能不能连授权才解决能干什么。新用户建出来如果不配任何ACL规则、默认策略又是allow那就是畅通无阻。5.3 订阅通配符导致的越权前面讲过订阅检查的是过滤器本身。如果规则里写了allow subscribe #这种很危险那客户端能订任何主题。哪怕你本意是只允许它订自己的一旦写成#全完了。我建议永远不要在生产规则的订阅主题里用裸#至少加上命名空间前缀。另外的位置也要小心device//control看起来限定了control但能匹配任何设备等于允许它订所有设备的control主题这在多租户场景里是越权。5.4 大小写和空格问题主题是大小写敏感的。Device/001和device/001是两个完全不同的主题规则里大小写不一致匹配就会失败。空格同理很多从表格粘贴过来的规则会带上首尾空格肉眼看不出来机器一匹配就错。建议规则里的主题用统一的命名规范全小写或驼峰保持一致粘贴后检查一遍。5.5 外部数据源的缓存延迟如果你用了外部数据源比如Redis或HTTP改完规则不是立即生效的因为EMQX有缓存有些版本默认缓存时间不短。表现就是我数据库都改了怎么还没生效。解决办法是调小缓存时间会增加查询压力或者手动清缓存/重启授权源或者干脆在测试阶段用内置ACL验证逻辑。这个坑我在上外部授权时吃过一度以为是代码bug查了半天才发现是缓存。5.6 客户端重连与权限更新规则改了之后已连接的客户端在下一次动作时会用新规则这点是好的。但有些SDK会把订阅状态缓存在本地你以为它断了其实还连着旧会话。所以变更权限后稳妥做法是让相关客户端重连一次或者用会话清理机制clean session相关配置确保状态一致。6. 从能用走向好用ACL设计的几个经验教训6.1 主题命名规范是权限设计的地基我现在做任何MQTT项目第一步不是写ACL而是定主题规范。一个我常用的结构是业务域/租户或设备ID/方向/具体功能。比如iot/tenantA/dev001/up和iot/tenantA/dev001/down/cmd。有了这个结构ACL规则就能很自然地写成基于前缀的allow租户之间天然隔离设备之间也隔离。反过来如果主题是随手起的ACL只能一条条硬写维护成本爆炸。6.2 最小权限不是口号是省事给这个设备开大点权限免得后面不够用——这种想法害人不浅。权限开大了第一不安全第二以后想收紧时你得先搞清楚现有的消息流依赖了哪些主题改起来牵一发动全身。我的做法是先给最窄的权限跑不通再加宁可多改几次规则也别一次性放太宽。这个顺序反过来做基本没有回头路。6.3 规则的可维护性规则多了以后可读性直接决定维护效率。我的习惯按业务域或设备类型分组加注释说明每组规则干嘛的。主体值用统一的变量用户名、clientid而不是硬编码一堆具体值。定期清理不再使用的规则别让它变成一坨没人敢动的东西。用外部数据源时把规则存成结构化的表配上变更记录比散落在配置文件里强得多。6.4 压测与权限的相互作用有个容易被忽略的点ACL检查是有成本的。规则特别多、又走了外部查询每次都要问一次数据库/HTTP在压测高并发时会成为瓶颈。我遇到过加了外部ACL后QPS明显下降的情况后来通过加缓存、精简规则、把常用规则挪到内置库解决了。所以ACL不是配完就完事还得关注它对吞吐的影响尤其是设备规模上来之后。6.5 安全审计与日志留存生产环境里授权拒绝的日志值得留存和定期看。它不仅能帮你发现配置错误还能暴露异常行为——比如某个设备突然开始尝试访问不属于它的主题这往往意味着设备被入侵或程序有bug。我一般会把这类日志集中收集设个简单的告警。这个习惯帮我提前发现过好几次异常访问尝试事后看都是没配好导致的误伤但宁可信其有。最后分享一个我自己的操作习惯每次改ACL我都会先在测试环境用MQTTX把正反用例跑一遍再上生产而且改完必看一小段日志确认拒绝和放行都符合预期。权限这东西配错了往往没有明显报错等发现时可能已经出事了。花五分钟验证比事后排查半天划算得多。
返回列表