MQTT 发布订阅 ACL -- EMQ X ACL 功能全插件介绍

概述

比官网详细,EMQ X MQTT 发布订阅 ACL 权限教程指南,包括 MySQL ACL、Redis ACL、MongoDB ACL、以及 HTTP 认证全部插件。

EMQ X 支持通过客户端发布订阅 ACL 进行客户端权限的管理,本文介绍了 EMQ X 支持的发布订阅 ACL 以及对应插件的MQTT 发布订阅权限配置管理的方法。

EMQ X MQTT Broker 简介

EMQ X Broker 是基于高并发的 Erlang/OTP 语言平台开发,支持百万级连接和分布式集群架构,发布订阅模式的开源 MQTT 消息服务器。

EMQ X Broker 在全球物联网市场广泛应用。无论是产品原型设计、物联网创业公司、还是大规模的商业部署,EMQ X Broker 都支持开源免费使用。

EMQ 官网Github 项目

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 是允许与拒绝条件的集合,EMQ X 中使用以下元素描述 ACL 规则:

[权限:允许|拒绝] [对象:IP 地址|用户名|Client ID] [动作:发布|订阅|发布订阅] [目标:主题]

同时具有多条 ACL 规则时,EMQ X 将按照规则排序进行合并,以 ACL 文件 中的默认 ACL 为例,ACL 文件中配置了默认的 ACL 规则,规则从下至上加载:

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

{allow, {ipaddr, "127.0.0.1"}, pubsub, ["$SYS/#", "#"]}.

{deny, all, subscribe, ["$SYS/#", {eq, "#"}]}.

{allow, all}.

授权结果

任何一次 ACL 授权最终都会返回一个结果:

  • 允许:经过检查允许客户端进行操作
  • 禁止:经过检查禁止客户端操作
  • 忽略(ignore):未查找到 ACL 权限信息,无法显式判断结果是允许还是禁止,交由下一 ACL 插件或默认 ACL 规则来判断

全局配置

默认配置中 ACL 是开放授权的,即授权结果为忽略(ignore)允许客户端通过授权。

通过 etc/emqx.conf 中的 ACL 配置可以更改该属性:

# etc/emqx.conf

## ACL 未匹配时默认授权
## Value: allow | deny
acl_nomatch = allow

配置默认 ACL 文件,使用文件定义默认 ACL 规则:

# etc/emqx.conf

acl_file = etc/acl.conf

配置 ACL 授权结果为禁止的响应动作,为 ignore 时将断开设备:

# etc/emqx.conf

## Value: ignore | disconnect
acl_deny_action = ignore

超级用户(superuser)

客户端可拥有“超级用户”身份,超级用户拥有最高权限不受 ACL 限制。

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

ACL 缓存

客户端发布、订阅频率较高的情况下开启 ACL 缓存可以提高 ACL 检查性能。

etc/emqx.conf 可以配置 ACL 缓存大小与缓存时间:

# etc/emqx.conf

## 是否启用
enable_acl_cache = on

## 单个客户端最大缓存规则数量
acl_cache_max_size = 32

## 缓存失效时间,超时后缓存将被清除
acl_cache_ttl = 1m

ACL 鉴权链

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

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

同时只启用一个 ACL 插件可以提高客户端 ACL 检查性能。

内置 ACL

内置 ACL 通过文件设置规则,使用上足够简单轻量,适用于规则数量可预测、无变动需求或变动较小的项目。

ACL 规则文件:

etc/acl.conf

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

定义 ACL

内置 ACL 使用 Erlang 语法进行规则定义,规则从下到上依次覆盖,定义方式为:

[权限:允许|拒绝] [对象:IP 地址|用户名|Client ID] [动作:发布|订阅|发布订阅] [目标:主题]

你可以在主题中使用以下占位符,ACL 授权检查时 EMQ X 将自动填充为客户端信息:

  • %u:用户名
  • %c:Client ID
  • %a:客户端 IP 地址

默认规则文件:etc/acl.conf

# etc/acl.conf

{allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}.

{allow, {ipaddr, "127.0.0.1"}, pubsub, ["$SYS/#", "#"]}.

{deny, all, subscribe, ["$SYS/#", {eq, "#"}]}.

{allow, all}.

EMQ X 启动时将读取 ACL 文件并将数据加载到内存中,节点上的 ACL 数据会在此阶段同步至集群。

修改改 ACL 文件后需要使用 ./bin/emqx_ctl acl reload 命令或重启 EMQ X 以更新规则。

MySQL ACL

MySQL ACL 使用外部 MySQL 数据库存储 ACL 规则,可以存储大量数据、动态管理 ACL,方便与外部设备管理系统集成

插件:

emqx_auth_mysql

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

MySQL 连接信息

MySQL 基础连接信息,需要保证集群内所有节点均能访问。

# etc/plugins/emqx_auth_mysql.conf

## 服务器地址
auth.mysql.server = 127.0.0.1:3306

## 连接池大小
auth.mysql.pool = 8

auth.mysql.username = emqx

auth.mysql.password = public

auth.mysql.database = mqtt

auth.mysql.query_timeout = 5s

默认表结构

MySQL 认证插件默认配置下需要确保数据库中有以下两张数据表,用于存储认证规则信息:

认证/超级用户表

CREATE TABLE `mqtt_user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(100) DEFAULT NULL,
`password` varchar(100) DEFAULT NULL,
`salt` varchar(35) DEFAULT NULL,
`is_superuser` (1) DEFAULT 0,
`created` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `mqtt_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

示例数据:

-- 客户端信息
INSERT INTO `mqtt_user` ( `username`, `password`, `salt`, `is_superuser`)
VALUES
('emqx', 'efa1f375d76194fa51a3556a97e641e61685f914d446979da50a551a4333ffd7', NULL, 0);

ACL 规则表

CREATE TABLE `mqtt_acl` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`allow` int(1) DEFAULT 1 COMMENT '0: deny, 1: allow',
`ipaddr` varchar(60) DEFAULT NULL COMMENT 'IpAddress',
`username` varchar(100) DEFAULT NULL COMMENT 'Username',
`clientid` varchar(100) DEFAULT NULL COMMENT 'ClientId',
`access` int(2) NOT NULL COMMENT '1: subscribe, 2: publish, 3: pubsub',
`topic` varchar(100) NOT NULL DEFAULT '' COMMENT 'Topic Filter',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

规则表字段说明:

  • allow:禁止(0),允许(1)
  • ipaddr:设置 IP 地址
  • username:连接客户端的用户名,此处的值如果设置为 $all 表示该规则适用于所有的用户
  • clientid:连接客户端的 Client ID
  • access:允许的操作:订阅(1),发布(2),订阅发布都可以(3)
  • topic:控制的主题,可以使用通配符,并且可以在主题中加入占位符来匹配客户端信息,例如 t/%c 则在匹配时主题将会替换为当前客户端的 Client ID
    • %u:用户名
    • %c:Client ID

默认配置下示例数据:

-- 所有用户不可以订阅系统主题
INSERT INTO mqtt_acl (allow, ipaddr, username, clientid, access, topic) VALUES (0, NULL, '$all', NULL, 1, '$SYS/#');

-- 允许 10.59.1.100 上的客户端订阅系统主题
INSERT INTO mqtt_acl (allow, ipaddr, username, clientid, access, topic) VALUES (1, '10.59.1.100', NULL, NULL, 1, '$SYS/#');

-- 禁止客户端订阅 /smarthome/+/temperature 主题
INSERT INTO mqtt_acl (allow, ipaddr, username, clientid, access, topic) VALUES (0, NULL, NULL, NULL, 1, '/smarthome/+/temperature');

-- 允许客户端订阅包含自身 Client ID 的 /smarthome/${clientid}/temperature 主题
INSERT INTO mqtt_acl (allow, ipaddr, username, clientid, access, topic) VALUES (1, NULL, NULL, NULL, 1, '/smarthome/%c/temperature');

启用 MySQL ACL 后并以用户名 emqx 成功连接后,客户端应当数据具有相应的主题权限。

这是默认配置使用的表结构,熟悉该插件的使用后你可以使用任何满足条件的数据表进行 ACL 规则存储。

超级用户 SQL(super_query)

进行 ACL 鉴权时,EMQ X 将使用当前客户端信息填充并执行用户配置的超级用户 SQL,查询客户端是否为超级用户。客户端为超级用户时将跳过 ACL SQL。

# etc/plugins/emqx_auth_mysql.conf

auth.mysql.super_query = select is_superuser from mqtt_user where username = '%u' limit 1

你可以在 SQL 中使用以下占位符,执行时 EMQ X 将自动填充为客户端信息:

  • %u:用户名
  • %c:Client ID
  • %C:TLS 证书公用名(证书的域名或子域名),仅当 TLS 连接时有效
  • %d:TLS 证书 subject,仅当 TLS 连接时有效

你可以根据业务需要调整超级用户 SQL,如添加多个查询条件、使用数据库预处理函数,以实现更多业务相关的功能。但是任何情况下超级用户 SQL 需要满足以下条件:

  1. 查询结果中必须包含 is_superuser 字段,is_superuser 应该显式的为 true
  2. 查询结果只能有一条,多条结果时只取第一条作为有效数据

如果不需要超级用户功能,注释并禁用该选项能有效提高效率

ACL SQL(acl_query)

进行 ACL 鉴权时,EMQ X 将使用当前客户端信息填充并执行用户配置的超级用户 SQL,如果没有启用超级用户 SQL 或客户端不是超级用户,则使用 ACL SQL 查询出该客户端在数据库中的 ACL 规则。

# etc/plugins/emqx_auth_mysql.conf

auth.mysql.acl_query = select allow, ipaddr, username, clientid, access, topic from mqtt_acl where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c'

你可以在 ACL SQL 中使用以下占位符,执行时 EMQ X 将自动填充为客户端信息:

  • %u:用户名
  • %c:Client ID
  • %C:TLS 证书公用名(证书的域名或子域名),仅当 TLS 连接时有效
  • %d:TLS 证书 subject,仅当 TLS 连接时有效

你可以根据业务需要调整 ACL SQL,如添加多个查询条件、使用数据库预处理函数,以实现更多业务相关的功能。但是任何情况下 ACL SQL 需要满足以下条件:

  1. 查询结果中必须包含 allow、access、topic、clientid、username、ipaddr 字段,如果字段不想参与比对则使用 $all 字符串或者数据库 NULL
  2. 查询结果可以有多条,多条结果时按照从上到下的顺序进行匹配

可以在 SQL 中调整查询条件、指定排序方式实现更高效率的查询。

特殊说明

MySQL 8.0 及以后版本使用了 caching_sha2_password 作为默认身份验证插件,受限于客户端驱动你必须将其更改为 mysql_native_password 插件:

ALTER USER 'your_username'@'your_host' IDENTIFIED WITH mysql_native_password BY 'your_password';

PostgreSQL ACL

PostgreSQL ACL 使用外部 PostgreSQL 数据库存储 ACL 规则,可以存储大量数据、动态管理 ACL,方便与外部设备管理系统集成。

插件:

emqx_auth_pgsql

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

PostgreSQL 连接信息

PostgreSQL 基础连接信息,需要保证集群内所有节点均能访问。

# etc/plugins/emqx_auth_pgsql.conf

## 服务器地址
auth.pgsql.server = 127.0.0.1:5432

## 连接池大小
auth.pgsql.pool = 8

auth.pgsql.username = root

auth.pgsql.password = public

auth.pgsql.database = mqtt

auth.pgsql.encoding = utf8

## TLS 配置
## auth.pgsql.ssl = false
## auth.pgsql.ssl_opts.keyfile =
## auth.pgsql.ssl_opts.certfile =

默认表结构

PostgreSQL 认证插件默认配置下需要确保数据库中有以下两张数据表,用于存储认证规则信息:

认证/超级用户表

CREATE TABLE mqtt_user (
id SERIAL primary key,
is_superuser boolean,
username character varying(100),
password character varying(100),
salt character varying(40)
)

示例数据:

-- 客户端信息
INSERT INTO mqtt_user (username, password, salt, is_superuser)
VALUES
('emqx', 'efa1f375d76194fa51a3556a97e641e61685f914d446979da50a551a4333ffd7', NULL, false);

ACL 规则表

CREATE TABLE mqtt_acl (
id SERIAL primary key,
allow integer,
ipaddr character varying(60),
username character varying(100),
clientid character varying(100),
access integer,
topic character varying(100)
)

规则表字段说明:

  • allow:禁止(0),允许(1)
  • ipaddr:设置 IP 地址
  • username:连接客户端的用户名,此处的值如果设置为 $all 表示该规则适用于所有的用户
  • clientid:连接客户端的 Client ID
  • access:允许的操作:订阅(1),发布(2),订阅发布都可以(3)
  • topic:控制的主题,可以使用通配符,并且可以在主题中加入占位符来匹配客户端信息,例如 t/%c 则在匹配时主题将会替换为当前客户端的 Client ID
    • %u:用户名
    • %c:Client ID

默认配置下示例数据:

-- 所有用户不可以订阅系统主题
INSERT INTO mqtt_acl (allow, ipaddr, username, clientid, access, topic) VALUES (0, NULL, '$all', NULL, 1, '$SYS/#');

-- 允许 10.59.1.100 上的客户端订阅系统主题
INSERT INTO mqtt_acl (allow, ipaddr, username, clientid, access, topic) VALUES (1, '10.59.1.100', NULL, NULL, 1, '$SYS/#');

-- 禁止客户端订阅 /smarthome/+/temperature 主题
INSERT INTO mqtt_acl (allow, ipaddr, username, clientid, access, topic) VALUES (0, NULL, NULL, NULL, 1, '/smarthome/+/temperature');

-- 允许客户端订阅包含自身 Client ID 的 /smarthome/${clientid}/temperature 主题
INSERT INTO mqtt_acl (allow, ipaddr, username, clientid, access, topic) VALUES (1, NULL, NULL, NULL, 1, '/smarthome/%c/temperature');

启用 PostgreSQL ACL 后并以用户名 emqx 成功连接后,客户端应当数据具有相应的主题权限。

这是默认配置使用的表结构,熟悉该插件的使用后你可以使用任何满足条件的数据表进行 ACL 规则存储。

超级用户 SQL(super_query)

进行 ACL 鉴权时,EMQ X 将使用当前客户端信息填充并执行用户配置的超级用户 SQL,查询客户端是否为超级用户。客户端为超级用户时将跳过 ACL SQL。

# etc/plugins/emqx_auth_pgsql.conf

auth.pgsql.super_query = select is_superuser from mqtt_user where username = '%u' limit 1

你可以在 SQL 中使用以下占位符,执行时 EMQ X 将自动填充为客户端信息:

  • %u:用户名
  • %c:Client ID
  • %C:TLS 证书公用名(证书的域名或子域名),仅当 TLS 连接时有效
  • %d:TLS 证书 subject,仅当 TLS 连接时有效

你可以根据业务需要调整超级用户 SQL,如添加多个查询条件、使用数据库预处理函数,以实现更多业务相关的功能。但是任何情况下超级用户 SQL 需要满足以下条件:

  1. 查询结果中必须包含 is_superuser 字段,is_superuser 应该显式的为 true
  2. 查询结果只能有一条,多条结果时只取第一条作为有效数据

如果不需要超级用户功能,注释并禁用该选项能有效提高效率

ACL SQL(acl_query)

进行 ACL 鉴权时,EMQ X 将使用当前客户端信息填充并执行用户配置的超级用户 SQL,如果没有启用超级用户 SQL 或客户端不是超级用户,则使用 ACL SQL 查询出该客户端在数据库中的 ACL 规则。

# etc/plugins/emqx_auth_pgsql.conf

auth.pgsql.acl_query = select allow, ipaddr, username, clientid, access, topic from mqtt_acl where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c'

你可以在 ACL SQL 中使用以下占位符,执行时 EMQ X 将自动填充为客户端信息:

  • %a:客户端地址
  • %u:用户名
  • %c:Client ID

你可以根据业务需要调整 ACL SQL,如添加多个查询条件、使用数据库预处理函数,以实现更多业务相关的功能。但是任何情况下 ACL SQL 需要满足以下条件:

  1. 查询结果中必须包含 allow、access、topic、clientid、username、ipaddr 字段,如果字段不想参与比对则使用 $all 字符串或者数据库 NULL
  2. 查询结果可以有多条,多条结果时按照从上到下的顺序进行匹配

可以在 SQL 中调整查询条件、指定排序方式实现更高效率的查询。

Redis ACL

Redis ACL 使用外部 Redis 数据库存储 ACL 规则,可以存储大量数据、动态管理 ACL,方便与外部设备管理系统集成

插件:

emqx_auth_redis

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

Redis 连接信息

Redis 基础连接信息,需要保证集群内所有节点均能访问。

# etc/plugins/emqx_auth_redis.conf

## 服务器地址
auth.redis.server = 127.0.0.1:6379

## 连接池大小
auth.redis.pool = 8

auth.redis.database = 0

auth.redis.password =

默认数据结构

Redis 认证插件默认配置下使用哈希表存储认证数据,使用 mqtt_user: 作为 Redis 键前缀,数据结构如下:

认证/超级用户

redis> hgetall mqtt_user:emqx
password public
salt wivwiv

默认配置下示例数据如下:

HMSET mqtt_user:emqx password public salt wivwiv

ACL 规则数据

## 格式
HSET mqtt_acl:[username clientid] [topic] [access]

## 结构
redis> hgetall mqtt_acl:emqx
testtopic/1 1

Redis ACL 一条规则中定义了发布、订阅或发布/订阅的信息,在规则中的都是允许列表。

规则字段说明:

  • ipaddr:设置 IP 地址
  • username:连接客户端的用户名,此处的值如果设置为 $all 表示该规则适用于所有的用户
  • clientid:连接客户端的 Client ID
  • access:允许的操作:订阅(1),发布(2),订阅发布都可以(3)
  • topic:控制的主题,可以使用通配符,并且可以在主题中加入占位符来匹配客户端信息,例如 t/%c 则在匹配时主题将会替换为当前客户端的 Client ID
    • %u:用户名
    • %c:Client ID

默认配置下示例数据:

HSET mqtt_acl:emqx # 1
HSET mqtt_acl:testtopic/2 2

启用 Redis ACL 后并以用户名 emqx 成功连接后,客户端应当数据具有相应的主题权限。

超级用户查询命令(super cmd)

进行 ACL 鉴权时,EMQ X 将使用当前客户端信息填充并执行用户配置的超级用户查询命令,查询客户端是否为超级用户。客户端为超级用户时将跳过 ACL 查询命令。

# etc/plugins/emqx_auth_redis.conf

auth.redis.super_cmd = HGET mqtt_user:%u is_superuser

你可以在命令中使用以下占位符,执行时 EMQ X 将自动填充为客户端信息:

  • %u:用户名
  • %c:Client ID
  • %C:TLS 证书公用名(证书的域名或子域名),仅当 TLS 连接时有效
  • %d:TLS 证书 subject,仅当 TLS 连接时有效

你可以根据业务需要调整超级用户查询命令,如添加多个查询条件、使用数据库预处理函数,以实现更多业务相关的功能。但是任何情况下超级用户查询命令需要满足以下条件:

  1. 查询结果中第一个数据必须为 is_superuser 数据

如果不需要超级用户功能,注释并禁用该选项能有效提高效率

ACL 查询命令(acl cmd)

进行 ACL 鉴权时,EMQ X 将使用当前客户端信息填充并执行用户配置的超级用户 SQL,如果没有启用超级用户 SQL 或客户端不是超级用户,则使用 ACL 查询命令查询出该客户端在数据库中的 ACL 规则。

# etc/plugins/emqx_auth_redis.conf

auth.redis.acl_cmd = HGETALL mqtt_acl:%u

你可以在 ACL 查询命令中使用以下占位符,执行时 EMQ X 将自动填充为客户端信息:

  • %u:用户名
  • %c:Client ID

你可以根据业务需要调整 ACL 查询命令,但是任何情况下 ACL 查询命令需要满足以下条件:

  1. 哈希中使用 topic 作为键,access 作为值

Redis ACL 规则需严格使用上述数据结构。
Redis ACL 中添加的所有规则都是 允许 规则,可以搭配 etc/emqx.confacl_nomatch = deny 使用。

MongoDB ACL

MongoDB ACL 使用外部 MongoDB 数据库存储 ACL 规则,可以存储大量数据、动态管理 ACL,方便与外部设备管理系统集成

插件:

emqx_auth_mongo

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

MongoDB 连接信息

MongoDB 基础连接信息,需要保证集群内所有节点均能访问。

# etc/plugins/emqx_auth_mongo.conf

## MongoDB 架构类型
##
## Value: single | unknown | sharded | rs
auth.mongo.type = single

## rs 模式需要设置 rs name
## auth.mongo.rs_set_name =

## 服务器列表,集群模式下使用逗号分隔每个服务器
## Examples: 127.0.0.1:27017,127.0.0.2:27017...
auth.mongo.server = 127.0.0.1:27017

auth.mongo.pool = 8

auth.mongo.login =

auth.mongo.password =

## auth.mongo.auth_source = admin

auth.mongo.database = mqtt

auth.mongo.query_timeout = 5s

## SSL 选项
# auth.mongo.ssl = false

## auth.mongo.ssl_opts.keyfile =

## auth.mongo.ssl_opts.certfile =

## auth.mongo.ssl_opts.cacertfile =

## MongoDB write mode.
##
## Value: unsafe | safe
## auth.mongo.w_mode =

## Mongo read mode.
##
## Value: master | slave_ok
## auth.mongo.r_mode =

## MongoDB 拓扑配置,一般情况下用不到,详见 MongoDB 官网文档
auth.mongo.topology.pool_size = 1
auth.mongo.topology.max_overflow = 0
## auth.mongo.topology.overflow_ttl = 1000
## auth.mongo.topology.overflow_check_period = 1000
## auth.mongo.topology.local_threshold_ms = 1000
## auth.mongo.topology.connect_timeout_ms = 20000
## auth.mongo.topology.socket_timeout_ms = 100
## auth.mongo.topology.server_selection_timeout_ms = 30000
## auth.mongo.topology.wait_queue_timeout_ms = 1000
## auth.mongo.topology.heartbeat_frequency_ms = 10000
## auth.mongo.topology.min_heartbeat_frequency_ms = 1000

默认数据结构

MongoDB 认证默认配置下需要确保数据库中有如下集合:

认证/超级集合

{
username: "user",
password: "password hash",
salt: "password salt",
is_superuser: false,
created: "2020-02-20 12:12:14"
}

示例数据:

use mqtt

db.mqtt_user.insert({
"username": "emqx",
"password": "efa1f375d76194fa51a3556a97e641e61685f914d446979da50a551a4333ffd7",
"is_superuser": false,
"salt": ""
})

ACL 规则集合

{
username: "username",
clientid: "clientid",
publish: ["topic1", "topic2", ...],
subscribe: ["subtop1", "subtop2", ...],
pubsub: ["topic/#", "topic1", ...]
}

MongoDB ACL 一条规则中定义了发布、订阅和发布/订阅的信息,在规则中的都是允许列表。

规则字段说明:

  • username:连接客户端的用户名
  • clientid:连接客户端的 Client ID
  • publish:允许发布的主题数值,支持通配符
  • subscribe:允许订阅的主题数值,支持通配符
  • pubsub:允许发布订阅的主题数值,支持通配符

主题可以使用通配符,并且可以在主题中加入占位符来匹配客户端信息,例如 t/%c 则在匹配时主题将会替换为当前客户端的 Client ID

  • %u:用户名
  • %c:Client ID

默认配置下示例数据:

use mqtt

## 所有用户不可以订阅、发布系统主题
## 允许客户端订阅包含自身 Client ID 的 /smarthome/${clientid}/temperature 主题
db.mqtt_acl.insert({
username: "$all",
clientid: "$all",
publish: ["#"],
subscribe: ["/smarthome/%c/temperature"]
})

启用 MongoDB ACL 后并以用户名 emqx 成功连接后,客户端应当数据具有相应的主题权限。

超级用户查询(super_query)

进行 ACL 鉴权时,EMQ X 将使用当前客户端信息填充并执行用户配置的超级用户查询,查询客户端是否为超级用户。客户端为超级用户时将跳过 ACL 查询。

# etc/plugins/emqx_auth_mongo.conf

## 启用超级用户
auth.mongo.super_query = on

## 超级查询使用集合
auth.mongo.super_query.collection = mqtt_user

## 超级用户使用字段
auth.mongo.super_query.super_field = is_superuser

## 超级用户查询选择器,多个条件使用逗号分隔
#auth.mongo.super_query.selector = username=%u, clientid=$all
auth.mongo.super_query.selector = username=%u

同一个选择器的多个条件时实际查询中使用 MongoDB and 查询:

db.mqtt_user.find({ 
"username": "wivwiv"
"clientid": "$all"
})

你可以在查询条件中使用以下占位符,执行时 EMQ X 将自动填充为客户端信息:

  • %u:用户名
  • %c:Client ID

你可以根据业务需要调整超级用户查询,如添加多个查询条件、使用数据库预处理函数,以实现更多业务相关的功能。但是任何情况下超级用户查询需要满足以下条件:

  1. 查询结果中必须包含 is_superuser 字段,is_superuser 应该显式的为 true

如果不需要超级用户功能,注释并禁用该选项能有效提高效率

ACL 查询(acl_query)

进行 ACL 鉴权时,EMQ X 将使用当前客户端信息填充并执行用户配置的超级用户查询,如果没有启用超级用户查询或客户端不是超级用户,则使用 ACL 查询 查询出该客户端在数据库中的 ACL 规则。

# etc/plugins/emqx_auth_mongo.conf

auth.mongo.acl_query = on

auth.mongo.acl_query.collection = mqtt_acl

## 查询选择器,多个条件时使用逗号分隔
## auth.mongo.acl_query.selector = username=%u,clientid=%c
auth.mongo.acl_query.selector = username=%u

## 使用多个查询选择器
## auth.mongo.acl_query.selector.1 = username=$all
## auth.mongo.acl_query.selector.2 = username=%u

同一个选择器的多个条件时实际查询中使用 MongoDB and 查询:

db.mqtt_acl.find({ 
"username": "emqx"
"clientid": "$all"
})

多个选择器时实际查询中使用 MongoDB or 查询:

db.mqtt_acl.find({
"$or": [
{
"username": "$all"
},
{
"username": "emqx"
}
]
})

你可以在 ACL 查询中使用以下占位符,执行时 EMQ X 将自动填充为客户端信息:

  • %u:用户名
  • %c:Client ID

MongoDB ACL 规则需严格使用上述数据结构。
MongoDB ACL 中添加的所有规则都是 允许 规则,可以搭配 etc/emqx.confacl_nomatch = deny 使用。

HTTP ACL

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

插件:

emqx_auth_http

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

要启用 HTTP ACL,需要在 etc/plugins/emqx_auth_http.conf 中配置以下内容:

ACL 授权原理

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

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

HTTP 请求信息

HTTP API 基础请求信息,配置证书、请求头与重试规则。

# etc/plugins/emqx_auth_http.conf

## 启用 HTTPS 所需证书信息
## auth.http.ssl.cacertfile = etc/certs/ca.pem

## auth.http.ssl.certfile = etc/certs/client-cert.pem

## auth.http.ssl.keyfile = etc/certs/client-key.pem

## 请求头设置
## auth.http.header.Accept = */*

## 重试设置
auth.http.request.retry_times = 3

auth.http.request.retry_interval = 1s

auth.http.request.retry_backoff = 2.0

进行发布、订阅认证时,EMQ X 将使用当前客户端信息填充并发起用户配置的 ACL 授权查询请求,查询出该客户端在 HTTP 服务器端的授权数据。

superuser 请求

首先查询客户端是否为超级用户,客户端为超级用户时将跳过 ACL 查询。

# etc/plugins/emqx_auth_http.conf

## 请求地址
auth.http.super_req = http://127.0.0.1:8991/mqtt/superuser

## HTTP 请求方法
## Value: post | get | put
auth.http.super_req.method = post

## 请求参数
auth.http.super_req.params = clientid=%c,username=%u

ACL 授权查询请求

# etc/plugins/emqx_auth_http.conf

## 请求地址
auth.http.acl_req = http://127.0.0.1:8991/mqtt/acl

## HTTP 请求方法
## Value: post | get | put
auth.http.acl_req.method = get

## 请求参数
auth.http.acl_req.params = access=%A,username=%u,clientid=%c,ipaddr=%a,topic=%t,mountpoint=%m

请求说明

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 连接时有效

推荐使用 POST 与 PUT 方法,使用 GET 方法时明文密码可能会随 URL 被记录到传输过程中的服务器日志中。