认证
身份认证是物联网应用的重要组成部分,可以帮助有效阻止非法客户端的连接。为了提供更好的安全保障,EMQX 支持多种认证机制,此外,EMQX 还支持 TLS 的双向认证(X.509 证书认证)和基于 PSK 的 TLS/DTLS 认证,在一定程度上满足了客户端和服务端之间的身份验证要求。
本节将向您介绍 EMQX 认证的基本概念和使用方式。
TIP
EMQX 默认未开启认证功能,即允许所有客户端链接,如在生产环境中使用,请提前配置好至少一种认证方法。
认证机制
EMQX 支持的认证机制包括 X.509 证书认证、JWT 认证、密码认证、基于 MQTT 5.0 协议的增强认证以及 PSK 认证。
X.509 证书认证
EMQX 支持使用 X.509 证书认证 进行客户端认证。通过在 EMQX 中使用 X.509 证书认证,客户端和服务器可以通过 TLS/SSL 建立安全连接,确保通信双方的真实性和传输数据的完整性。EMQX 支持单向和双向认证:单向认证中,只有服务器被客户端认证;双向认证中,客户端和服务器相互验证对方的证书。这种灵活性适应了不同级别的安全需求和部署场景。
JWT 认证
JSON Web Token(JWT) 是一种基于 Token 的认证机制,它不需要服务器来保留客户端的认证信息或会话信息。客户端可以在密码或用户名中携带 Token,EMQX 通过预先配置的密钥或公钥对 JWT 签名进行验证。
此外,如果用户配置了 JWKS 端点,EMQX 也支持通过从 JWKS 端点查询到的公钥列表对 JWT 签名进行验证,从而能够批量为客户端签发认证信息。
密码认证
密码认证是最简单,也是使用最多的认证方式。此时,客户端需要提供能够表明身份的凭据,例如用户名、客户端 ID 以及对应的密码,或是 TLS 证书中的一些字段(例如证书公用名称)。这些身份凭据会提前存储到特定数据源(数据库)中,密码通常都会以加盐后散列的形式存储。
EMQX 支持通过密码进行身份验证。启用密码认证后,当客户端尝试连接时,需按要求提供身份凭证信息,EMQX 会在数据库中发起查询,并将返回得到的密码与客户端提供的信息进行匹配,匹配成功后,EMQX 将接受该客户端的连接请求。
除简单便捷的内置数据库外,EMQX 还支持通过与多类后端数据库的集成提供密码认证,包括 MySQL、PostgreSQL、MongoDB 和 Redis。
此外,EMQX 也支持通过 HTTP 方式对接用户自己开发的服务,借此实现更复杂的认证逻辑。
MQTT 5.0 增强认证
MQTT 5.0 增强认证是对密码认证的扩展,增强认证特性允许使用各种更安全的认证机制,例如 SCRAM 认证、Kerberos 认证等。目前 EMQX 具体实施了 SCRAM 认证,并支持将认证数据存储在内置数据库中或通过 REST API 访问外部 HTTP 服务获取认证数据。
PSK 认证
EMQX 中的 PSK 认证 提供了一个更简单但同样安全的替代方案,取代了基于证书的 TLS 认证。它依赖于客户端和服务器都知道的共享密钥,从而绕过了对数字证书的需求。这种机制在资源受限的环境中特别有用,因为在这些环境中处理证书的开销可能相当大。
EMQX 认证器
按照认证方式和数据源来划分,EMQX 内置了以下认证器:
认证方式 | 数据源 | 说明 |
---|---|---|
密码认证 | 内置数据库 | 使用内置数据库(Mnesia)进行密码认证 |
密码认证 | MySQL | 使用 MySQL 进行密码认证 |
密码认证 | PostgreSQL | 使用 PostgreSQL 进行密码认证 |
密码认证 | MongoDB | 使用 MongoDB 进行密码认证 |
密码认证 | Redis | 使用 Redis 进行密码认证 |
密码认证 | LDAP | 使用 LDAP 进行密码认证 |
密码认证 | HTTP Server | 使用 HTTP 服务进行密码认证 |
JWT | -- | JWT 认证 |
增强认证 | 内置数据库 | MQTT 5.0 增强认证(SCRAM 认证) |
增强认证 | HTTP 服务 | 基于 REST API 的 MQTT 5.0 增强认证 (SCRAM 认证) |
增强认证 | Kerberos | MQTT 5.0 增强认证 - Kerberos |
规则认证 | -- | Client-info 认证 |
认证链
EMQX 允许创建多个认证器构成一条认证链,认证器将按照在链中的位置顺序执行,如果在当前认证器中未检索到身份凭证,将会切换至链上的下一个启用的认证器继续认证。
TIP
认证链中不允许出现多个相同类型的认证器。同时,只有 MQTT 协议支持认证链,网关仅支持使用单个认证器。
认证流程
以密码认证为例,通常这会产生以下 2 种情况:
- 当前认证器执行时检索到了匹配的认证信息,例如用户名一致:
- 密码完全匹配,则客户端认证通过,允许连接。
- 密码无法匹配,则客户端认证失败,拒绝连接。
- 当前认证器执行时没有检索到匹配的认证信息,例如数据源中没有查找到数据:
- 当前认证器之后还有认证器:忽略认证,交由下一认证器继续认证。
- 当前认证器已经是链中最后一个认证器:客户端认证失败,拒绝连接。
TIP
请注意,某个认证器查找出错(如数据库错误)或未启用时,将继续执行下一个认证器。
应用场景
用户可以创建多个认证器实现链式认证,某些场景下这是有益的,比如客户端数量多、连接速率很高的场景下,用户可能使用 Redis 作为链中的第一个认证器,与 HTTP Server 认证器搭配使用,借助 Redis 高性能优势提供缓存层以实现更高性能的认证能力。
超级用户与权限
通常情况下,认证只是验证了客户端的身份是否合法,而该客户端是否具备发布、订阅某些主题的权限,还需要由授权系统来判断。
EMQX 允许在认证阶段为客户端设置超级用户角色以及预设权限,用于后续的发布订阅权限检查。
TIP
目前 JWT 认证和 HTTP 认证支持权限预设,允许通过 JWT Payload 和 HTTP 响应体携带当前客户端拥有的发布订阅权限列表,并在认证成功后预设到客户端。
超级用户的判定发生在认证阶段,由数据库查询结果、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 = 32 # optional, Unit: Byte
}
注意,不同散列算法之间可能存在较大的性能差异,请酌情选择。作为参考,以下是在 4 核 8GB 的机器中将各散列算法运行 100 次后取得的平均运行时间:
认证占位符
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,即使 EMQX 部署在某些 TCP 代理或负载均衡器之后,用户也可以使用此占位符获得真实 IP 地址。${peerport}
: 它将在运行时被客户端的 IP 端口替换。${cert_subject}
: 将在运行时被替换为客户端 TLS 证书的主题(Subject)。如果证书信息是从负载均衡器发送到 EMQX 的 TCP 端口,需要确保负载均衡器使用的是 Proxy Protocol v2。${cert_common_name}
: 将在运行时被替换为客户端 TLS 证书的通用名称(Common Name)。如果证书信息是从负载均衡器发送到 EMQX 的 TCP 端口,需要确保负载均衡器使用的是 Proxy Protocol v2。${client_attrs.NAME}
:某个客户端属性。NAME
将在运行时根据预定义配置替换为属性名称。有客户端属性的详细信息,请参见 MQTT 客户端属性。${zone}
: 在运行时将替换为客户端的 Zone。${zone}
占位符可以直接用于认证模板中,简化规则创建,并支持基于 Zone 的特定配置。有关 Zone 的详细配置信息,请参见 Zone 覆盖。例如,以下 ACL 规则使用
${zone}
根据客户端的指定 zone 动态应用权限:{allow, all, all, ["${zone}/${username}/#"]}
认证配置方式
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 = {
...
}
}
不同类型的认证器有着不同的配置项要求。关于各配置项的具体配置方法,可参考配置说明文档 ,其中包含了每种认证器的所有配置字段的详细说明。
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。