# 认证
身份认证是物联网应用的重要组成部分,可以帮助有效阻止非法客户端的连接。为了提供更好的安全保障,EMQX 支持多种认证机制,如密码认证,JWT 认证,以及基于 MQTT 5.0 协议的增强认证。
此外,EMQX 还支持 TLS 的双向认证(X.509 证书认证)和基于 PSK 的 TLS/DTLS 认证,在一定程度上满足了客户端和服务端之间的身份验证要求。
本章节将向您介绍 EMQX 认证的基本概念和使用方式。
提示
EMQX 默认未开启认证功能,即允许所有客户端链接,如在生产环境中使用,请提前配置好至少一种认证方法。
# 认证方式
# 密码认证
密码认证是最简单,也是使用最多的认证方式。此时,客户端需要提供能够表明身份的凭据,例如用户名、客户端 ID 以及对应的密码,或是 TLS 证书中的一些字段(例如证书公用名称)。这些身份凭据会提前存储到特定数据源(数据库)中,密码通常都会以加盐后散列的形式存储。
EMQX 支持通过密码进行身份验证。启用密码认证后,当客户端尝试连接时,需按要求提供身份凭证信息,EMQX 会在数据库中发起查询,并将返回得到的密码与客户端提供的信息进行匹配,匹配成功后,EMQX 将接受该客户端的连接请求。

除简单便捷的内置数据库外,EMQX 还支持通过与多类后端数据库的集成提供密码认证,包括 MySQL、PostgreSQL、MongoDB 和 Redis。
此外,EMQX 也支持通过 HTTP 方式对接用户自己开发的服务,借此实现更复杂的认证逻辑。
# JWT 认证
JSON Web Token(JWT) (opens new window) 是一种基于 Token 的认证机制,它不需要服务器来保留客户端的认证信息或会话信息。客户端可以在密码或用户名中携带 Token,EMQX 通过预先配置的密钥或公钥对 JWT 签名进行验证。
此外,如果用户配置了 JWKS 端点,EMQX 也支持通过从 JWKS 端点查询到的公钥列表对 JWT 签名进行验证,从而能够批量为客户端签发认证信息。
# MQTT 5.0 增强认证
MQTT 5.0 增强认证 (opens new window)是对密码认证的扩展,增强认证特性允许使用各种更安全的认证机制,例如 SCRAM 认证、Kerberos 认证等。目前 EMQX 具体实施了 SCRAM 认证,并支持将认证数据存储在内置数据库中。
# EMQX 认证器
按照认证方式和数据源来划分,EMQX 内置了以下 8 种认证器:
认证方式 | 数据源 | 说明 |
---|---|---|
密码认证 | 内置数据库 | 使用内置数据库(Mnesia)进行密码认证 |
密码认证 | MySQL | 使用 MySQL 进行密码认证 |
密码认证 | PostgreSQL | 使用 PostgreSQL 进行密码认证 |
密码认证 | MongoDB | 使用 MongoDB 进行密码认证 |
密码认证 | Redis | 使用 Redis 进行密码认证 |
密码认证 | HTTP Server | 使用 HTTP 服务进行密码认证 |
JWT | -- | JWT 认证 |
增强认证 | 内置数据库 | MQTT 5.0 增强认证(SCRAM 认证) |
# 基本概念
# 认证链
EMQX 允许创建多个认证器构成一条认证链,认证器将按照在链中的位置顺序执行,如果在当前认证器中未检索到身份凭证,将会切换至链上的下一个启用的认证器继续认证。
提示
认证链中不允许出现多个相同类型的认证器。同时,只有 MQTT 协议支持认证链,网关仅支持使用单个认证器。
# 认证流程
以密码认证为例,通常这会产生以下 2 种情况:
- 当前认证器执行时检索到了匹配的认证信息,例如用户名一致:
- 密码完全匹配,则客户端认证通过,允许连接。
- 密码无法匹配,则客户端认证失败,拒绝连接。
- 当前认证器执行时没有检索到匹配的认证信息,例如数据源中没有查找到数据:
- 当前认证器之后还有认证器:忽略认证,交由下一认证器继续认证。
- 当前认证器已经是链中最后一个认证器:客户端认证失败,拒绝连接。
提示
请注意,某个认证器查找出错(如数据库错误)或未启用时,将继续执行下一个认证器。
# 应用场景
用户可以创建多个认证器实现链式认证,某些场景下这是有益的,比如客户端数量多、连接速率很高的场景下,用户可能使用 Redis 作为链中的第一个认证器,与 HTTP Server 认证器搭配使用,借助 Redis 高性能优势提供缓存层以实现更高性能的认证能力。
# 监听器认证
默认情况下 EMQX 所有监听器接入的客户端都使用同一种认证方式,从同一个认证数据源中读取数据。但在同时接入多个服务的 EMQX 集群中,用户可能需要根据业务不同为每种接入方式配置不同的认证方式,比如:
- 通过 MQTT over WebSocket 接入的客户端不会颁发永久的用户名密码凭证,而是使用具有时效性的 JWT 进行认证以确保业务安全;
- 通过 MQTT TCP 接入的硬件设备会在初始化时烧录用户名密码或客户端证书,该认证凭证在整个生命周期中不会变化,可以使用密码认证;
- 用于后端服务连接的监听器不需要认证检查,此监听器通常监听于内网地址有足够的安全性。
提示
同样,目前只有 MQTT 监听器支持使用认证链,网关监听器仅支持使用单个认证器。
# 认证器优先级
监听器身份认证器的优先级高于全局身份认证器。监听器会优先匹配自身身份认证器的规则设置,如未设置或已移除,才会使用全局身份认证器。
# 应用场景
例如,对于启用了 TLS 身份认证的监听器,用户可能不希望应用全局身份认证;此外,如果网络中存在来自不同供应商的客户端设备,也需要启用监听器级别的身份认证来解决设备重名问题。
# 超级用户与权限
通常情况下,认证只是验证了客户端的身份是否合法,而该客户端是否具备发布、订阅某些主题的权限,还需要由授权系统来判断。
EMQX 允许在认证阶段为客户端设置超级用户角色以及预设权限,用于后续的发布订阅权限检查。
提示
权限预设目前仅 JWT 认证支持,允许通过 JWT Payload 携带当前客户端拥有的发布订阅权限列表,并在认证成功后预设到客户端。
超级用户的判定发生在认证阶段,由数据库查询结果、HTTP 响应或者 JWT 声明中的 is_superuser
字段来指示。
# 密码散列
通过明文存储密码会有非常高的密码泄漏风险。因此 EMQX 支持多种密码散列算法以满足不同用户的安全性要求,同时我们建议生成一个随机的盐,数据库中存储盐与对密码加盐后散列得到的值(password_hash)。
# 工作原理
密码散列算法配置在认证时的原理如下:
- 认证器使用配置的查询语句从数据库中查询符合条件的身份凭证,包括散列密码和盐值;
- 根据认证器配置的散列算法和查询到的盐值,对客户端连接时提供的密码进行散列;
- 将第 1 步从数据库查询到的散列密码和第 2 步计算出的散列值进行比较,一致则说明客户端的身份合法。
以下为 EMQX 目前支持的散列算法:
# simple algorithms
password_hash_algorithm {
name = sha256 # plain, md5, sha, sha512
salt_position = suffix # prefix, disable
}
# bcrypt
password_hash_algorithm {
name = bcrypt
}
# pbkdf2
password_hash_algorithm {
name = pbkdf2
mac_fun = sha256 # md4, md5, ripemd160, sha, sha224, sha384, sha512
iterations = 4096
dk_length = 256 # optional
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 认证占位符
EMQX 允许使用占位符动态构造认证数据查询语句、HTTP 请求,占位符会在认证器执行时替换为真实的客户端信息,以构造出与当前客户端匹配的查询语句或 HTTP 请求。
以 MySQL 认证器为例,默认的查询 SQL 中使用了 ${username}
占位符:
SELECT password_hash, salt FROM mqtt_user where username = ${username} LIMIT 1
当用户名为 emqx_u
的客户端连接认证时,实际执行认证数据查询 SQL 将被替换为:
SELECT password_hash, salt FROM mqtt_user where username = 'emqx_u' LIMIT 1
目前 EMQX 支持以下占位符:
${clientid}
: 将在运行时被替换为客户端 ID。客户端 ID 一般由客户端在CONNECT
报文中显式指定,如果启用了use_username_as_clientid
或peer_cert_as_clientid
,则会在连接时被用户名、证书中的字段或证书内容所覆盖。${username}
: 将在运行时被替换为用户名。用户名来自CONNECT
报文中的Username
字段。如果启用了peer_cert_as_username
,则会在连接时被证书中的字段或证书内容所覆盖。${password}
: 将在运行时被替换为密码。密码来自CONNECT
报文中的Password
字段。${peerhost}
: 将在运行时被替换为客户端的 IP 地址。EMQX 支持 Proxy Protocol (opens new window),即使 EMQX 部署在某些 TCP 代理或负载均衡器之后,用户也可以使用此占位符获得真实 IP 地址。${cert_subject}
: 将在运行时被替换为客户端 TLS 证书的主题(Subject),仅适用于 TLS 连接。${cert_common_name}
: 将在运行时被替换为客户端 TLS 证书的通用名称(Common Name),仅适用于 TLS 连接。
# 配置方式
EMQX 提供了 3 种使用认证的配置方式,分别为:Dashboard、配置文件和 HTTP API。
# Dashboard
Dashboard 底层调用了 HTTP API,提供了相对更加易用的可视化操作页面。在 Dashboard 中可以方便的查看认证器状态、调整认证器在认证链中的位置,如下图所示,我们已经成功添加了基于内置数据库和 JWT 两种认证机制。
# 配置文件
EMQX 支持为 MQTT 客户端配置多个认证器以组成认证链 ,如以下代码示例中的 authentication
字段所示,认证器在数组中的顺序便是在认证链中执行的顺序:
# emqx.conf
# Specific global authentication chain for all MQTT listeners
authentication = [
...
]
listeners.tcp.default {
...
enable_authn = true
# Specific authentication chain for the specified MQTT listener
authentication = [
...
]
}
gateway.stomp {
...
enable_authn = true
# Specific global authenticator for all STOMP listeners
authentication = {
...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
不同类型的认证器有着不同的配置项要求。关于各配置项的具体配置方法,可参考配置说明文档 ,其中包含了每种认证器的所有配置字段的详细说明。
# HTTP API
与配置文件相比,HTTP API 支持运行时更新,能够自动将配置改动同步至整个集群,使用起来更加方便。
EMQX 提供的认证 API 允许对认证链和认证器进行管理,例如为全局认证创建一个认证器,以及更新指定认证器的配置。
/api/v5/authentication
: 管理 MQTT 全局认证/api/v5/gateway/{protocol}/authentication
: 管理网关的全局认证/api/v5/gateway/{protocol}/listeners/{listener_id}/authentication
: 管理网关监听器认证
# 认证器 ID
如果想要对指定认证器进行操作,则需要在上面这些端点后面追加一个认证器 ID,例如 /api/v5/authentication/{id}
。为了便于维护,这里的 ID 并不是 EMQX 自动生成然后由 API 返回的,而是遵循了一套预先定义的规范:
<mechanism>:<backend>
或者仅仅只有:
<mechanism>
例如:
password_based:built_in_database
jwt
scram:built_in_database
同样,对于监听器 ID,我们也有一套类似的约定,MQTT 监听器 ID 的格式为:
<transport_protocol>:<name>
网关监听器 ID 的格式为:
<protocol>:<transport_protocol>:<name>
我们可以把 MQTT 监听器 ID 看作是默认省略了最前面的协议名。
注意,不管是认证器 ID,还是监听器 ID,当它们在 URL 中使用时,都需要遵循 URL 编码规范。最直接的,我们需要将 :
替换为 %3A
,示例:
PUT /api/v5/authentication/password_based%3Abuilt_in_database
# 数据操作 API
对于通过内置数据库存储认证数据的认证方式,例如 使用内置数据库进行密码认证 和 MQTT 5.0 增强认证,EMQX 提供了相关的 HTTP API 来管理认证数据,如创建、更新、删除和查看等操作,具体可阅读 通过 HTTP API 管理用户。
详细的请求方式与参数请参考 HTTP API。