EMQ高级功能使用

发布订阅ACL

发布订阅ACL简介

发布订阅ACL是指对发布(publish)/订阅(subscribe)操作的权限控制。例如拒绝用户 emq-demo 向 testTopic/a主题发布消息。 EMQ X 支持通过客户端发布订阅 ACL 进行客户端权限的管理。

ACL 插件

EMQ X 支持使用配置文件外部主流数据库自定义 HTTP API 作为 ACL 数据源。
连接数据源、进行访问控制功能是通过插件实现的,使用前需要启用相应的插件。
客户端订阅主题、发布消息时插件通过检查目标主题(Topic)是否在指定数据源允许/禁止列表内来实现对客户端的发布、订阅权限管理。

  • 内置 ACL
    使用配置文件提供认证数据源,适用于变动较小的 ACL 管理。
  • 外部数据库
    MySQL ACL
    PostgreSQL ACL
    Redis ACL
    MongoDB ACL
    外部数据库可以存储大量数据、动态管理 ACL,方便与外部设备管理系统集成。
  • HTTP ACL
    HTTP ACL 能够实现复杂的 ACL 管理
    ACL 功能包含在认证鉴权插件中,更改插件配置后需要重启插件才能生效,

ACL规则详解

ACL 是允许与拒绝条件的集合,EMQ X 中使用以下元素描述 ACL 规则:

## Allow-Deny Who Pub-Sub Topic 
"允许(Allow) / 拒绝(Deny)" "谁(Who)" "订阅(Subscribe) / 发布(Publish)" "主题列表(Topics)"

同时具有多条 ACL 规则时,EMQ X 将按照规则排序进行合并,以 ACL 文件中的默认 ACL 为例,ACL 文件中配置了默认的 ACL 规则,规则从下至上加载
在这里插入图片描述

  1. 第一条规则允许客户端发布订阅所有主题
  2. 第二条规则禁止全部客户端订阅 $SYS/# 与 # 主题
  3. 第三条规则允许 ip 地址为 127.0.0.1 的客户端发布/订阅 $SYS/# 与 # 主题,为第二条开了特例
  4. 第四条规则允许用户名为 dashboard 的客户端订阅 $SYS/# 主题,为第二条开了特例

授权结果

任何一次 ACL 授权最终都会返回一个结果:
允许:经过检查允许客户端进行操作
禁止:经过检查禁止客户端操作
忽略(ignore):未查找到 ACL 权限信息(no match),无法显式判断结果是允许还是禁止,交由下一ACL 插件或默认 ACL 规则来判断

全局配置

默认配置中 ACL 是开放授权的,即授权结果为忽略(ignore)时允许客户端通过授权。
通过 etc/emqx.conf 中的 ACL 配置可以更改该属性
在这里插入图片描述
此处我们需要修改全局配置文件中关于acl的配置,将 acl_nomatch 配置项的值改为: deny
完成配置后使用 emqx restart 重启emqx broker服务
在这里插入图片描述
在这里插入图片描述
配置默认 ACL 文件,使用文件定义默认 ACL 规则
配置 ACL 授权结果为禁止的响应动作,为 disconnect 时将断开设备

超级用户

客户端在进行认证的时候客户端可拥有“超级用户”身份,超级用户拥有最高权限不受 ACL 限制。

  • 认证鉴权插件启用超级用户功能后,发布订阅时 EMQ X 将优先检查客户端超级用户身份
  • 客户端为超级用户时,通过授权并跳过后续 ACL 检查

ACL缓存

ACL 缓存允许客户端在命中某条 ACL 规则后,便将其缓存至内存中,以便下次直接使用,客户端发布、订阅
频率较高的情况下开启 ACL 缓存可以提高 ACL 检查性能。
在 etc/emqx.conf 可以配置 ACL 缓存大小与缓存时间:
在这里插入图片描述
清除缓存:
在更新 ACL 规则后,某些客户端由于已经存在缓存,则无法立即生效。若要立即生效,则需手动清除所有的ACL 缓存,清除缓存需要需要使用EMQ X Broker提供的监控管理的HTTP API
查询指定客户端的 ACL 缓存: GET /api/v4/clients/{clientid}/acl_cache

##############查询指定客户端的 ACL 缓存#################### 
GET http://{{hostname}}:{{port}}/api/v4/clients/emq-client1/acl_cache HTTP/1.1 
Content-Type: {{contentType}} 
Authorization: Basic {{userName}}:{{password}}

清除指定客户端的ACL缓存: DELETE /api/v4/clients/{clientid}/acl_cache

##############清除指定客户端的 ACL 缓存#################### 
DELETE http://{{hostname}}:{{port}}/api/v4/clients/emq-client1/acl_cache HTTP/1.1 
Content-Type: {{contentType}} 
Authorization: Basic {{userName}}:{{password}}

测试时注意开启对应的插件

ACL 鉴权链

当同时启用多个 ACL 插件时,EMQ X 将按照插件开启先后顺序进行链式鉴权:

  • 一通过授权,终止链并允许客户端通过验证
  • 一旦授权失败,终止链并禁止客户端通过验证
  • 直到最后一个 ACL 插件仍未通过,根据默认授权配置判定
    • 默认授权为允许时,允许客户端通过验证
    • 默认授权为禁止时,禁止客户端通过验证

在这里插入图片描述
同时只启用一个 ACL 插件可以提高客户端 ACL 检查性能。

内置ACL

内置 ACL 优先级最低,可以被 ACL 插件覆盖,如需禁用全部注释即可。规则文件更改后需重启 EMQ X 以应用生效。

定义ACL

%% 允许 "dashboard" 用户 订阅 "$SYS/#" 主题 
{allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}. 
%% 允许 IP 地址为 "127.0.0.1" 的用户 发布/订阅 "#SYS/#""#" 主题 
{allow, {ipaddr, "127.0.0.1"}, pubsub, ["$SYS/#", "#"]}. 
%% 拒绝 "所有用户" 订阅 "$SYS/#" "#" 主题 
{deny, all, subscribe, ["$SYS/#", {eq, "#"}]}. 
%% 允许其它任意的发布订阅操作 
{allow, all}.

acl.conf 编写规则

规则按书写顺序从上往下匹配。注意,匹配是从上往下,规则加载是从下往上加载

  • 以 %% 表示行注释。
  • 每条规则由四元组组成,以 . 结束。
  • 元组第一位:表示规则命中成功后,执行权限控制操作,可取值为:
    allow :表示 允许 deny : 表示 拒绝
  • 元组第二位:表示规则所生效的用户,可使用的格式为:
    {user, “dashboard”} :表明规则仅对 用户名 (Username) 为 “dashboard” 的用户生效
    {clientid, “dashboard”} :表明规则仅对 客户端标识 (ClientId) 为 “dashboard” 的用户生效
    {ipaddr, “127.0.0.1”} :表明规则仅对 源地址 为 “127.0.0.1” 的用户生效
    all :表明规则对所有的用户都生效
  • 元组第三位:表示规则所控制的操作,可取值为:
    publish :表明规则应用在 PUBLISH 操作上
    subscribe :表明规则应用在 SUBSCRIBE 操作上
    pubsub :表明规则对 PUBLISH 和 SUBSCRIBE 操作都有效
  • 元组第四位:表示规则所限制的主题列表,内容以数组的格式给出,例如:
    “$SYS/#” :为一个 主题过滤器 (Topic Filter);表示规则可命中与 $SYS/# 匹配的主题;如:可
    命中 $SYS/# ,也可命中 $SYS/a/b/c {eq, “#”} :表示字符的全等,规则仅可命中主题为 # 的字串,不能命中 /a/b/c 等
  • 除此之外还存在两条特殊的规则:
    {allow, all} :允许所有操作
    {deny, all} :拒绝所有操作

在 acl.conf 修改完成后,并不会自动加载至 EMQ X 系统。需要手动执行:

./bin/emqx_ctl acl reload

acl.conf 中应只包含一些简单而通用的规则,使其成为系统基础的 ACL 原则。如果需要支持复杂、大量的 ACL内容,需要使用认证插件。

HTTP ACL

HTTP 认证使用外部自建 HTTP 应用认证授权数据源,根据 HTTP API 返回的数据判定授权结果,能够实现复杂的 ACL 校验逻辑。

emqx_auth_http

注意:emqx_auth_http 插件同时包含认证功能,可通过注释禁用。

ACL授权原理

EMQ X 在设备发布、订阅事件中使用当前客户端相关信息作为参数,向用户自定义的认证服务发起请求权限,通过返回的 HTTP 响应状态码 (HTTP statusCode) 来处理 ACL 授权请求。

  • 无权限:API 返回 4xx 状态码
  • 授权成功:API 返回 200 状态码
  • 忽略授权:API 返回 200 状态码且消息体 ignore

HTTP 请求信息

在这里插入图片描述
要启用 HTTP ACL,需要在 etc/plugins/emqx_auth_http.conf 中配置
与认证HTTP请求相同的HTTP API 基础请求信息,配置证书、请求头与重试规则。
在这里插入图片描述
进行发布、订阅认证时,EMQ X 将使用当前客户端信息填充并发起用户配置的 ACL 授权查询请求,查询出该客户端在 HTTP 服务器端的授权数据。

superuser 请求和 ACL 授权查询请求

首先查询客户端是否为超级用户,客户端为超级用户时将跳过 ACL 查询。
在这里插入图片描述
请求说明
HTTP 请求方法为 GET 时,请求参数将以 URL 查询字符串的形式传递;POST、PUT 请求则将请求参数以普通表单形式提交(content-type 为 x-www-form-urlencoded)。
你可以在认证请求中使用以下占位符,请求时 EMQ X 将自动填充为客户端信息:
%u:用户名
%c:Client ID
%a:客户端 IP 地址
%r:客户端接入协议
%P:明文密码
%p:客户端端口
%C:TLS 证书公用名(证书的域名或子域名),仅当 TLS 连接时有效
%d:TLS 证书 subject,仅当 TLS 连接时有效
%m:topic的安装点,是桥接的连接属性
推荐使用 POST 与 PUT 方法,使用 GET 方法时明文密码可能会随 URL 被记录到传输过程中的服务器日志中。

HTTP ACL接口开发

在原有的项目 emq-demo 中我们已经开发了基于HTTP API的认证Controller: AuthController ,按照我们的请求URL配置,我们需要在该Controller中添加两个接口方法,一个是用于查询superuser的,一个是用于进行ACL授权查询的,这两个方法分别如下:
1:查询客户端是否为超级用户

@PostMapping("/superuser")
public ResponseEntity superuser(@RequestParam("clientid") String clientid,
                                @RequestParam("username") String username){

    log.info("emqx 查询是否是超级用户,clientid={},username={}",clientid,username);
    
    if(clientid.contains("admin") || username.contains("admin")){
        log.info("用户{}是超级用户",username);
        return new ResponseEntity(HttpStatus.OK);
    }else {
        log.info("用户{}不是超级用户",username);
        return new ResponseEntity(HttpStatus.UNAUTHORIZED);
    }
    
}

注意,我们在初始化方法init中添加一个超级用户:admin/admin

@PostConstruct
public void init(){
    users = new HashMap<>();
    users.put("user","123456");
    users.put("emq-client2","123456");//testtopic/#
    users.put("emq-client3","123456");// testtopic/123
    users.put("admin","admin");
}

同理:API返回200状态码代表是超级用户,API返回4XX状态码则不是超级用户
2:ACL 授权查询请求

@PostMapping("/acl")
public ResponseEntity acl(@RequestParam("access")int access,
                          @RequestParam("username")String username,
                          @RequestParam("clientid")String clientid,
                          @RequestParam("ipaddr")String ipaddr,
                          @RequestParam("topic")String topic,
                          @RequestParam("mountpoint")String mountpoint){
    log.info("EMQX发起客户端操作授权查询请求,access={},username={},clientid={},ipaddr={},topic={},mountpoint={}",
            access,username,clientid,ipaddr,topic,mountpoint);
    
    
    if(username.equals("emq-client2") && topic.equals("testtopic/#") && access  == 1){
        log.info("客户端{}有权限订阅{}",username,topic);
        return new ResponseEntity(HttpStatus.OK);
    }
    
    if(username.equals("emq-client3") && topic.equals("testtopic/123") && access == 2){
        log.info("客户端{}有权限向{}发布消息",username,topic);
        return new ResponseEntity(HttpStatus.OK);
    }

    log.info("客户端{},username={},没有权限对主题{}进行{}操作",clientid,username,topic,access==1?"订阅":"发布");
    
    return new ResponseEntity(HttpStatus.UNAUTHORIZED);
}

在这个方法中我们设置了:
只有用户名为 emq-client2 的客户端能够去订阅 testtopic/# 的权限其他客户端都不可以
只有用户名为 emq-client3 的客户端能够向 testtopic/123 发布消息的权限其他都没有

HTTP ACL接口测试

1:首先测试超级用户,在Dashboard中打开插件 emqx_auth_http ,其他的认证插件emqx_auth_clientid , emqx_auth_username 可以暂时停掉
使用MQTTX客户端工具:创建一个新的连接,username/password使用超级用户:admin/admin
在这里插入图片描述
在这里插入图片描述
然后我们用此客户端订阅主题: testtopic/# ,预期能够订阅成功并且不会发起ACL查询请求
接着我们用此客户端向主题: testtopic/123 发布消息,预期能够发布成功并且不会发起ACL查询请求
在这里插入图片描述
2:创建一个客户端,用户名密码使用:emq-client2/123456,如下
在这里插入图片描述

在这里插入图片描述
然后我们用该客户端订阅主题 testtopic/# ,然后查看服务控制台输出
在这里插入图片描述
接着我们用该客户端向主题 testtopic/123 发送消息,查看服务控制台输出
在这里插入图片描述

在这里插入图片描述
此时注意该客户端,因为成功订阅了 testtopic/# ,如果我们的消息发布成功了我们应该能收到消息,事实是
没有收到消息,证明消息发布没有成功!
同理:可以在创建一个客户端使用:emq-client3/123456,该客户端只能向 testtopic/123 中发布消息但是
不能订阅 testtopic/#

WebHook

WebHook简介

WebHook 是由 emqx_web_hook 插件提供的 将 EMQ X 中的钩子事件通知到某个 Web 服务 的功能。

钩子(Hooks) 是 EMQ X 提供的一种机制,它通过拦截模块间的函数调用、消息传递、事件传递来修改或扩展系统功能。
简单来讲,该机制目的在于增强软件系统的扩展性、方便与其他三方系统的集成、或者改变其系统原有的默认行为。如下图
在这里插入图片描述
当系统中不存在 钩子 (Hooks) 机制时,整个事件处理流程 从 事件 (Event) 的输入,到 处理 (Handler),
再到完成后的返回 结果 (Result) 对于系统外部而讲,都是不可见、且无法修改的。
而在这个过程中加入一个可挂载函数的点 (HookPoint),允许外部插件挂载多个回调函数,形成一个调
用链。达到对内部事件处理过程的扩展和修改。系统中常用到的认证插件则是按照该逻辑进行实现的。
因此,在 EMQ X 中,钩子 (Hooks) 这种机制极大地方便了系统的扩展。我们不需要修改 emqx 核心代
码,仅需要在特定的位置埋下 挂载点 (HookPoint) ,便能允许外部插件扩展 EMQ X 的各种行为。

对于实现者来说仅需要关注:

  1. 挂载点 (HookPoint) 的位置:包括其作用、执行的时机、和如何挂载和取消挂载。
  2. 回调函数 的实现:包括回调函数的入参个数、作用、数据结构等,及返回值代表的含义。
  3. 了解回调函数在 上执行的机制:包括回调函数执行的顺序,及如何提前终止链的执行。
    如果你是在开发扩展插件中使用钩子,你应该能 完全地明白这三点,且尽量不要在钩子内部使用阻塞函数,这会影响系统的吞吐

WebHook 的内部实现是基于钩子,但它更靠近顶层一些。它通过在钩子上的挂载回调函数,获取到 EMQ X中的各种事件,并转发至 emqx_web_hook 中配置的 Web 服务器。
以 客户端成功接入(client.connected) 事件为例,其事件的传递流程如下:
在这里插入图片描述
WebHook 对于事件的处理是单向的,它仅支持将 EMQ X 中的事件推送给 Web 服务,并不关心 Web 服务的返回。 借助 Webhook 可以完成设备在线、上下线记录,订阅与消息存储、消息送达确认等诸多业务。

配置项说明

Webhook 的配置文件位于 etc/plugins/emqx_web_hook.conf :
在这里插入图片描述
说明:当消息内容是不可见字符(如二进制数据)时,为了能够在 HTTP 协议中传输,使用 encode_payload是十分有用的。

配置触发规则:

在 etc/plugins/emqx_web_hooks.conf 可配置触发规则,其配置的格式如下:

## 格式示例 
web.hook.rule.<Event>.<Number> = <Rule> 
## 示例值 
web.hook.rule.message.publish.1 = {"action": "on_message_publish", "topic": "a/b/c"} 
web.hook.rule.message.publish.2 = {"action": "on_message_publish", "topic": "foo/#"}

Event 触发事件:

在这里插入图片描述

Number

同一个事件可以配置多个触发规则,配置相同的事件应当依次递增。

Rule

触发规则,其值为一个 JSON 字符串,其中可用的 Key 有:

  • action:字符串,取固定值,每种事件下规则中的action在 etc/plugins/emqx_web_hooks.conf 文件中有定义
    在这里插入图片描述
  • topic:字符串,表示一个主题过滤器,操作的主题只有与该主题匹配才能触发事件的转发
    例如,我们只将与 a/b/c 和 foo/# 主题匹配的消息转发到 Web 服务器上,其配置应该为:
web.hook.rule.message.publish.1 = {"action": "on_message_publish", "topic": "a/b/c"} 
web.hook.rule.message.publish.2 = {"action": "on_message_publish", "topic": "foo/#"}

这样 Webhook 仅会转发与 a/b/c 和 foo/# 主题匹配的消息,例如 foo/bar 等,而不是转发 a/b/d 或 fo/bar

Webhook事件参数

事件触发时 Webhook 会按照配置将每个事件组成一个 HTTP 请求发送到 api.url 所配置的 Web 服务器上。其请求格式为:
在这里插入图片描述

对于不同的事件,请求 Body 体内容有所不同,下表列举了各个事件中 Body 的参数列表:
client.connect
在这里插入图片描述
client.connack
在这里插入图片描述
client.connected
在这里插入图片描述
client.disconnected
在这里插入图片描述
client.subscribe
在这里插入图片描述
opts 包含
在这里插入图片描述
client.unsubscribe
在这里插入图片描述
message.publish
在这里插入图片描述
message.delivered
在这里插入图片描述

Webhook实现客户端断连监控

断连监控需求

系统需要知道所有客户端当前的连接状态,方便在后台管理系统中进行直观展示

代码实现

通过EMQX 的webhook将客户端的连接断开等事件通知到我们自建的服务上,通过事件类型获取客户端的连接状态,然后将客户端的连接状态进行存储,并且提供HTTP API供后台系统查询所有客户端的状态
1:在原有项目 emq-demo 中创建:com.itheima.controller.mqtt.WebHookController

package com.itheima.controller.mqtt;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

/**
 * 
 */
@RestController
@RequestMapping("/mqtt")
public class WebHookController {
    private static final Logger log  = LoggerFactory.getLogger(WebHookController.class);
    
    private Map<String,Boolean> clientStatus = new HashMap<>();
    
    @PostMapping("/webhook")
    public void hook(@RequestBody Map<String,Object> params){
        log.info("emqx 触发 webhook,请求体数据={}",params);

        String action = (String) params.get("action");
        String clientId = (String) params.get("clientid");
        if(action.equals("client_connected")){
            log.info("客户端{}接入本系统",clientId);
            clientStatus.put(clientId,true);
        }
        
        if(action.equals("client_disconnected")){
            log.info("客户端{}下线",clientId);
            clientStatus.put(clientId,false);
        }
        
    }
    
    @GetMapping("/allStatus")
    public Map getStatus(){
        return this.clientStatus;
    }
}

hook方法用来接收EMQ X传入过来的请求,将客户端Id的连接状态记录到map中,getAllStatus方法用来返回所有客户端状态。
然后通过客户端连接/断开EMQ X之后,通过访问 all 接口就能得到这些客户端得状态了。当然了,在实际的项目中肯定就不会这么简单,我们会将这些客户端的状态存入类似redis这样的分布式缓存中,方便整个系统进行
存取随时获取客户端状态。
2:修改配置文件 /etc/plugins/emqx_web_hook.conf ,配置webhook访问地址
在这里插入图片描述
关于事件规则:该配置文件中已默认配置了客户端成功连接和断开的事件及规则,我们就不需要在配置了,如下

web.hook.rule.client.connected.1 = {"action": "on_client_connected"} 
web.hook.rule.client.disconnected.1 = {"action": "on_client_disconnected"}

其他事件的默认配置如果不想要可以注释掉
3:在EMQX Dashboard中开启 emqx_web_hook 插件
在这里插入图片描述

测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yemuxiaweiliang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值