news 2026/9/23 1:57:58

EMQX 认证器前置条件(Authenticator Precondition):基于客户端信息的条件化认证调度

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
EMQX 认证器前置条件(Authenticator Precondition):基于客户端信息的条件化认证调度
  • 后端
  • 物联网
  • 消息队列
  • 通信

【免费下载链接】emqx

The most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles

项目地址:https://gitcode.com/gh_mirrors/em/emqx
点击查看免费下载

导读

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)
peersniTLS 客户端发送的 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_eqis_empty_valiifnot)的行为可在 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/1do_compile_precondition/1undefined与空二进制<<>>都被视为"无前置条件"(编译结果为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

项目地址:https://gitcode.com/gh_mirrors/em/emqx
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 1:57:59

移动流量卡监控:手写实现高可用流量告警系统实战

移动流量卡监控:手写实现高可用流量告警系统实战 面试被问“怎么监控服务器流量异常”,很多人只能答个“看监控大盘”。真正能拿Offer的,是能手写实现一套轻量级流量探针,实时捕获突发流量并触发告警。今天我们就以【移动流量卡】业务为场景,从零搭建一个基于Python的实时流量监控服务。这不只是练手,更是…

作者头像 李华
网站建设 2026/9/23 1:57:51

mu5344报错全解析:面试必问的Stack Trace排查心法

mu5344报错全解析:面试必问的Stack Trace排查心法 盯着屏幕上那串红色的英文字母,头都大了。报错信息长得像天书,Java 的 StackTrace 更是直接给你甩出几十行堆栈,光看 NullPointerException 或者…

作者头像 李华
网站建设 2026/9/23 1:57:38

3个坑教你怎么给照片换背景,附避坑指南

3个坑教你怎么给照片换背景,附避坑指南 你从GitHub复制的 rembg 代码,运行后背景全黑或者报错 ImportError ,根本不知道怎么调?别急,这种“复制粘贴即崩溃”的噩梦太常见了。今天这份 避坑指南 ,不聊虚的,直接带你扒开开源库的底裤,看看代码到底怎么跑的。…

作者头像 李华
网站建设 2026/9/23 1:57:36

双人坦克大战源码拆解:3个关键逻辑避坑指南

双人坦克大战源码拆解:3个关键逻辑避坑指南 刚把老项目里的 Canvas 游戏引擎升级到最新 Web API,打开控制台全是报错。 requestAnimationFrame 的时间戳处理变了, touchstart…

作者头像 李华
网站建设 2026/9/23 1:57:28

别只背八股文,手机app源码里的性能优化才是面试通关密码

别只背八股文,手机app源码里的性能优化才是面试通关密码 上周刚帮一个朋友复盘面试,他在腾讯二面挂了。面试官没问什么高并发、分布式,就指着屏幕上一段简单的数据加载代码问:“如果这里改成异步,内存占用会怎么变?主线程阻塞多久会掉帧?”他愣了三秒,支支吾吾说“大概会快点吧”,然后就被婉拒了。…

作者头像 李华
网站建设 2026/9/23 1:57:28

2026最新搜狗 mac面试突击:搞定代码跑不通的底层逻辑

2026最新搜狗 mac面试突击:搞定代码跑不通的底层逻辑 复制来的代码在 Mac 上直接报错,或者在搜狗输入法里输入时卡顿、内存飙升,这时候别慌。很多开发者以为这是输入法的问题,其实往往是因为环境配置、进程调度或者底层 API 调用没对齐。在 2026 年的最新技术栈下,Mac 系统的…

作者头像 李华