- 后端
- 物联网
- 消息队列
- 通信
【免费下载链接】emqx
The most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles
导读
EMQX 从 v5.x 起支持在认证链(Authentication Chain)中为每个认证器(Authenticator)配置前置条件(Precondition)表达式。本文基于 changes/ee/feat-14976.en.md 的变更记录展开,介绍如何在认证器中通过 Variform 表达式按客户端信息(如监听器、Zone、用户名等)选择性调用认证器,从而避免不必要的认证请求。读完本文你将掌握precondition配置项的语法、可用变量、真实配置示例,以及它在源码中的编译、渲染与执行机制。
功能背景与核心价值
EMQX 的认证(Authentication,authn)采用认证链机制:多个认证器按顺序排列,逐个尝试对客户端进行身份验证。在默认情况下,链上的每个认证器都会被依次调用,直到某个认证器返回成功或全部失败。
引入 precondition(前置条件)后,每个认证器可以附带一个 Variform 条件表达式。只有当该表达式求值为字符串"true"时,对应的认证器才会被真正调用;否则该认证器被跳过,直接进入链上的下一个认证器。这带来了两个直接收益:
- 避免不必要的认证请求:例如将 HTTP 认证器限定在
tcp:default监听器,将 PostgreSQL 认证器限定在ssl:default监听器,从而减少跨网络的后端请求; - 实现按场景分流:同一认证链可以按客户端来源、TLS 属性、用户名/密码特征等维度,路由到不同的认证后端。
该能力由 EMQX 认证模块 apps/emqx_auth 实现,涉及配置 Schema、认证链执行逻辑与 Variform 表达式引擎三部分。
配置项说明
precondition字段
在认证器(Authenticator)配置中,新增了可选字段precondition。它定义在认证链公共字段中,见 apps/emqx_auth/src/emqx_authn/emqx_authn_schema.erl#L200-L213:
- 类型:
binary()(字符串表达式); - 默认值:
<<>>(空字符串等价于未配置,表示该认证器无条件被调用); - 语义:一个 Variform 表达式,使用客户端信息作为预绑定变量求值;表达式求值结果必须为字符串
"true",认证器才会被调用;求值为任何其他值,则跳过该认证器。
可用变量
根据 rel/i18n/emqx_authn_schema.hocon#L168-L194 中的字段描述,表达式可用的预绑定变量(来源于客户端信息)包括:
| 变量 | 含义 |
|---|---|
username | 客户端用户名 |
password | 客户端密码 |
clientid | 客户端 ID |
client_attrs.* | 客户端属性 |
cert_common_name | 客户端 TLS 证书的 subject 字段 |
cert_subject | 客户端 TLS 证书的 CN(Common Name) |
peersni | TLS 客户端发送的 SNI(Server Name Indication) |
listener | 监听器 ID(例如tcp:default) |
zone | 客户端关联的配置 Zone |
常用表达式示例
文档 rel/i18n/emqx_authn_schema.hocon#L186-L192 给出的官方示例:
- 仅当客户端从监听器
ssl:letsencrypt接入时才调用该认证器:str_eq(listener, 'ssl:letsencrypt') - 当用户名为空时跳过该认证器:
not(is_empty_val(username)) - 仅当密码存在且 Zone 为
zone1时调用:iif(is_empty_val(password), false, str_eq(zone, 'zone1'))
这些内置函数(如str_eq、is_empty_val、iif、not)的行为可在 Variform 表达式引擎的单元测试 apps/emqx_utils/test/emqx_variform_tests.erl#L247-L253 与 apps/emqx_utils/test/emqx_variform_tests.erl#L384-L386 中验证,例如:
str_eq('a', 'a')求值为{ok, <<"true">>},str_eq('a', 'b')求值为{ok, <<"false">>};iif(str_eq(a,1),2,3)在a = 1时求值为2,否则为3。
配置示例:按监听器分流认证器
变更记录 changes/ee/feat-14976.en.md 中给出了典型场景:对通过tcp:default接入的客户端触发 HTTP 认证器,对通过ssl:default接入的客户端触发 PostgreSQL 认证器。
在 HOCON 配置文件中,可以在认证链(authentication)下为每个认证器添加precondition:
authentication = [ { mechanism = password_based backend = http method = post url = "http://127.0.0.1:8080/auth" precondition = "str_eq(listener, 'tcp:default')" enable = true }, { mechanism = password_based backend = postgresql server = "127.0.0.1:5432" database = "mqtt" username = "emqx" password = "secret" precondition = "str_eq(listener, 'ssl:default')" enable = true } ]运行效果:
- 通过
tcp:default监听器接入的客户端:HTTP 认证器前置条件满足 → 触发 HTTP 认证请求;PostgreSQL 认证器前置条件不满足 → 被跳过,不产生任何 PostgreSQL 查询; - 通过
ssl:default监听器接入的客户端:行为相反,仅 PostgreSQL 认证器被调用; - 两个监听器的客户端共用同一个认证链,但认证后端完全隔离。
源码实现解析
Schema 定义
precondition作为认证器公共字段与enable并列注册,见 apps/emqx_auth/src/emqx_authn/emqx_authn_schema.erl#L209-L213:
common_fields() -> [ {enable, fun enable/1}, {precondition, precondition()} ].编译期:表达式预编译
创建认证器时,配置中的 precondition 字符串会先经emqx_variform:compile/1编译为内部表达式(预编译可提前发现语法错误),编译失败时返回bad_precondition_expression错误:
- apps/emqx_auth/src/emqx_authn/emqx_authn_chains.erl#L1068-L1082 中的
compile_precondition/1与do_compile_precondition/1:undefined与空二进制<<>>都被视为"无前置条件"(编译结果为undefined); - 创建认证器的入口 apps/emqx_auth/src/emqx_authn/emqx_authn_chains.erl#L1028-L1035 与更新认证器的 apps/emqx_auth/src/emqx_authn/emqx_authn_chains.erl#L726-L752 都会执行编译,并把编译结果存入
#authenticator{precondition = ...}记录。
运行期:条件检查与跳过语义
认证链按顺序执行认证器时,会先检查 precondition:
- apps/emqx_auth/src/emqx_authn/emqx_authn_chains.erl#L923-L932 的
check_precondition/2:调用emqx_variform:render(Precondition, Credential)求值,结果恰好为二进制<<"true">>时返回{ok, true}; - apps/emqx_auth/src/emqx_authn/emqx_authn_chains.erl#L934-L952 的
authenticate_with_provider/2依据检查结果分三种情况处理:{ok, true}:调用do_authenticate_with_provider/2真正执行后端认证;{error, _}:表达式本身求值出错(如引用了不存在的变量),记为precondition_error并按后端失败处理;{ok, Other}:表达式求值成功但结果不是"true",记为precondition_not_met并返回ignore;
- apps/emqx_auth/src/emqx_authn/emqx_authn_chains.erl#L903-L906:
ignore结果不会终止认证链,而是继续尝试链上的下一个认证器;{stop, _}则终止。
因此,precondition 不满足的认证器对认证链而言等价于"未匹配"(nomatch 语义),既不会触发后端请求,也不会中断认证流程。
安全基线(Hardened Profile)下的组合
在加固安全基线(hardened security profile)下,precondition 与ignore_backend_failures语义可以组合使用,形成"按条件跳过 + 后端失败忽略"的弹性认证链。相关测试见 apps/emqx_auth/test/emqx_authn/emqx_authn_chains_SUITE.erl#L727-L762。
实战验证:JWT 与密码认证的混合链
测试套件 apps/emqx_auth/test/emqx_authn/emqx_authn_chains_SUITE.erl#L842-L856 演示了一个典型用法:在同一认证链中,先创建带前置条件is_jwt(password)的 JWT 认证器,再创建内置数据库(built_in_database)的密码认证器:
{ok, _} = ?AUTHN:create_authenticator(ListenerID, #{ mechanism => jwt, enable => true, precondition => <<"is_jwt(password)">> }), {ok, _} = ?AUTHN:create_authenticator(ListenerID, #{ mechanism => password_based, backend => built_in_database, enable => true }).测试验证的行为(apps/emqx_auth/test/emqx_authn/emqx_authn_chains_SUITE.erl#L735-L761):
- 当密码是 JWT 时:前置条件满足 → 调用 JWT 认证器;
- 当密码不是 JWT 时:前置条件不满足 → 跳过 JWT 认证器,回落到密码认证器;
- 当完全没有密码时:表达式求值为 false(非错误)→ 同样跳过,继续后续认证器。
这正体现了 precondition 的核心价值:把"是否适用"的判断从认证逻辑中剥离出来,交给可声明、可预编译的表达式,让认证链更简洁、更高效。
注意事项
precondition表达式的求值结果是字符串"true"(而非布尔值true),编写表达式时需注意函数的返回值形态;- 表达式求值失败(如变量不存在)会被视为认证失败而不是"跳过",与"表达式结果为 false 时跳过"语义不同,参见 apps/emqx_auth/src/emqx_authn/emqx_authn_chains.erl#L938-L944;
- 该配置同样适用于认证授权(authz)模块,其 Schema 中也有
precondition字段(见 apps/emqx_auth/src/emqx_authz/emqx_authz_schema.erl),实现思路一致; - 通过 Dashboard / HTTP API 创建认证器时,同样可以在请求体中携带
precondition字段;通过 Dashboard 界面配置时,该项位于认证器的"Precondition"设置中。
延伸阅读
- 认证链核心实现:apps/emqx_auth/src/emqx_authn/emqx_authn_chains.erl
- 认证器配置 Schema:apps/emqx_auth/src/emqx_authn/emqx_authn_schema.erl
- 字段说明与示例:rel/i18n/emqx_authn_schema.hocon
- Variform 表达式引擎:apps/emqx_utils/src/emqx_variform.erl 及其测试 apps/emqx_utils/test/emqx_variform_tests.erl
- 功能变更记录:changes/ee/feat-14976.en.md
- 后端
- 物联网
- 消息队列
- 通信
【免费下载链接】emqx
The most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles
相关推荐
EMQX 的 is_jwt 前置条件函数:在认证链中优雅分流 JWT 与传统凭据客户端
EMQX 的 is_jwt 前置条件函数:在认证链中优雅分流 JWT 与传统凭据客户端 导读 本文围绕 EMQX 6.2.3 新增的 is_jwt value
后端物联网消息队列通信CephFS 挂载前置条件指南:客户端选择、CephX 认证与配置文件准备
CephFS 挂载前置条件指南:客户端选择、CephX 认证与配置文件准备 本指南以 Ceph 官方文档 Mount CephFS: Prerequisites
存储分布式文件系统对象存储后端高可用EMQX 授权源前置条件(Precondition):基于 Variform 表达式的动态鉴权路由
EMQX 授权源前置条件(Precondition):基于 Variform 表达式的动态鉴权路由 导读 EMQX 的授权(Authorization/ACL)
后端物联网消息队列通信
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考