ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

EMQX client_attrs_init 支持 password 变量:用 JWT 密码初始化客户端属性

EMQX client_attrs_init 支持 password 变量:用 JWT 密码初始化客户端属性 EMQX client_attrs_init 支持 password 变量用 JWT 密码初始化客户端属性【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址: https://gitcode.com/gh_mirrors/em/emqx导读本篇文章围绕 EMQX 开源仓库中的变更记录 changes/ee/fix-16684.en.md 展开讲解mqtt.client_attrs_init表达式的一个关键能力增强在客户端属性初始化表达式中可以直接引用 CONNECT 报文携带的password。在此之前client_attrs_init的执行时机早于密码被写入渲染上下文导致依赖密码的表达式例如把密码当作 JWT 传给jwt_value提取声明无法解析。读完本文你将掌握client_attrs_init的完整配置方式、渲染上下文中可用的全部变量以及如何把 JWT 型密码中的子声明sub、user.name 等提取为客户端属性再用于挂载点、认证与授权模板渲染。一、变更背景什么是 client_attrs_initclient_attrs_init是 EMQX MQTT 配置mqtt命名空间下的一项能力用于在客户端连接建立时根据连接上下文计算一组客户端属性client attributes。这些属性会以client_attrs.{NAME}的形式存放并可以在后续的模板渲染中使用例如渲染挂载点mountpoint实现按租户隔离主题命名空间如mountpoint ${client_attrs.tns}/渲染 HTTP 认证 / 授权请求的请求体或头作为桥接、消息处理等场景中的变量来源。其配置结构定义在 apps/emqx/src/emqx_schema.erlfields(client_attrs_init) - [ {expression, emqx_variform:sc(#{desc ?DESC(client_attrs_init_expression)})}, {set_as_attr, sc(binary(), #{ desc ?DESC(client_attrs_init_set_as_attr), validator fun restricted_string/1 })} ];每个初始化条目由两个字段构成字段类型说明expression字符串一行 variform 表达式可以是函数调用支持嵌套、也可以是直接变量引用例如nth(1, tokens(clientid, .))提取 clientid 的点分前缀set_as_attr字符串计算结果存放的客户端属性名之后可通过client_attrs.{NAME}引用配置位于mqtt命名空间下见 apps/emqx/src/emqx_schema.erl以数组形式配置多个条目。set_as_attr还带有restricted_string校验器保证属性名符合安全规范。二、修复前的问题password 不在渲染上下文中按照变更记录 changes/ee/fix-16684.en.md 的描述此前的实现存在一个时序缺陷Previously,client_attrs_initran before password was added to the rendering context, so expressions depending on password could not be resolved.即client_attrs_init表达式求值发生在密码被加入渲染上下文之前。因此任何引用password的表达式都会解析失败。而密码恰恰是很多场景下的关键输入——尤其在密码本身就是 JWT例如设备接入使用 JWT 作为口令时我们希望直接从密码中提取sub、user.name、user.profile.age等声明来初始化客户端属性用于后续的租户识别、模板渲染。修复前这类表达式无法工作。三、修复后的实现password 进入渲染上下文修复的核心位于连接通道模块 apps/emqx/src/emqx_channel.erlget_client_attrs_init_config(Zone) - get_mqtt_conf(Zone, client_attrs_init, []). maybe_set_client_initial_attrs(ConnPkt, #{zone : Zone} ClientInfo, ConnInfo) - case get_client_attrs_init_config(Zone) of [] - {ok, ClientInfo}; Inits - UserProperty get_user_property_as_map(ConnPkt), Password get_connect_password(ConnPkt), RenderCtx client_attrs_init_render_ctx( ClientInfo#{user_property UserProperty, password Password}, ConnInfo ), Attrs0 maps:get(client_attrs, ClientInfo, #{}), Attrs1 initialize_client_attrs(Inits, RenderCtx), {ok, ClientInfo#{client_attrs maps:merge(Attrs0, Attrs1)}} end.关键变化通过get_connect_password/1从 CONNECT 报文中取出密码见 apps/emqx/src/emqx_channel.erl即#mqtt_packet_connect{password Password}将password与user_propertyCONNECT 报文的用户属性映射一起并入渲染上下文RenderCtxinitialize_client_attrs/2对每个条目执行emqx_variform:render(Variform, ClientInfo)并把结果按set_as_attr命名合并进client_attrs。从执行时机看maybe_set_client_initial_attrs位于enrich_client/3的流水线中见 apps/emqx/src/emqx_channel.erl顺序为设置 username → 设置 bridge_mode → 可能用 username 作为 clientid → 分配 clientid →初始化客户端属性→ clientid 覆盖。源码注释明确说明attr init should happen after clientid and username assign属性初始化应发生在 clientid 与 username 赋值之后、clientid override should happen after client_attrs is initializedclientid 覆盖应发生在属性初始化之后。这意味着在属性初始化表达式中clientid、username已经是最终值同时password也已可用。四、实战用 jwt_value 从密码中提取 JWT 声明jwt_value/2是 variform 提供的内置函数用于从 JWT token 中按点分路径提取声明值实现在 apps/emqx_utils/src/emqx_variform_bif.erl。其典型用法jwt_value(Token, sub)提取顶层声明jwt_value(Token, user.name)提取嵌套 JSON 对象中的字段jwt_value(Token, user.profile.age)提取更深层路径。从实现看jwt_value会把 token 按.拆分为三段header.payload.signature对 payload 做 base64url 解码后按点分路径取值若 token 结构非法会抛出invalid_jwt_token。需要说明的是该函数只解码 payload 并取值不做签名校验因此它适合在认证之前做属性预提取而不应单独作为身份认证手段。修复后即可在client_attrs_init表达式中引用password并喂给jwt_value例如在emqx.conf中mqtt { client_attrs_init [ { # 从 JWT 型密码中提取 sub 声明作为用户 ID expression jwt_value(password, sub) set_as_attr user_id }, { # 提取嵌套声明 user.name expression jwt_value(password, user.name) set_as_attr username_from_jwt } ] }表达式为单行 variform 语法支持函数嵌套调用与直接变量引用字符串字面量使用单引号与tokens(clientid, .)的写法一致。初始化完成后这些属性即可在其它模板中通过${client_attrs.user_id}、${client_attrs.username_from_jwt}引用例如挂载点mountpoint ${client_attrs.user_id}/实现按 JWT 主体隔离主题命名空间HTTP 认证 / 授权请求模板在请求体或 URL 中携带${client_attrs.user_id}作为查询参数。五、渲染上下文可用变量一览结合 schema 文档 rel/i18n/emqx_schema.hocon 与源码实现client_attrs_init表达式的预绑定变量包括变量来源说明clientidMQTT CONNECT客户端 ID属性初始化前已赋值usernameMQTT CONNECT用户名属性初始化前已赋值passwordMQTT CONNECT本次修复新增CONNECT 报文中的密码可喂给jwt_valueuser_property.{NAME}MQTT v5 CONNECTCONNECT 报文中的用户属性zone监听器配置客户端所在配置区zonelistener监听器配置监听器 ID如tcp:defaultcn/cert_common_nameTLS 证书客户端证书通用名直连或经 proxy-protocol v2 的 TLS 客户端dn/cert_subjectTLS 证书客户端证书主体DNcert_san.dns/cert_san.ip/cert_san.email/cert_san.uriTLS 证书直连 TLS 客户端的证书主题备用名称SANpeersniTLS 握手客户端发送的 SNI此外渲染上下文构建时还会合并证书 SAN 与 CN/DN 处理见 apps/emqx/src/emqx_channel.erl。需要注意的是仓库快照中的 schema 文档变量清单未显式列出password但源码已在maybe_set_client_initial_attrs/3中将其写入渲染上下文这是本次变更的实际行为。六、测试与行为验证仓库中的测试为这一机制提供了可验证依据客户端属性初始化的单元级验证位于 apps/emqx/test/emqx_channel_SUITE.erlt_client_attrs_with_control_chars_dropped直接调用emqx_channel:initialize_client_attrs/2传入#{set_as_attr ..., expression ...}列表验证渲染结果会按属性名合并且产生 C0/C1 控制字符例如从非严格模式的 MQTT v5 用户属性带入 CRLF的属性会被丢弃防止${client_attrs.tns}流入 HTTP 模板造成注入。jwt_value/2内置函数的单测位于 apps/emqx_utils/test/emqx_variform_bif_tests.erl覆盖了 JWT 声明提取、嵌套路径user.name、user.profile.age等场景。若要自行验证本次修复可在配置了上述client_attrs_init的 EMQX 节点上用一个密码为 JWT 的 MQTT 客户端发起连接然后通过 Dashboard 的客户端详情或client_attrs相关查询确认user_id等属性已按预期生成也可在 HTTP 认证 / 授权桥的请求模板中直接引用${client_attrs.user_id}观察实际透传值。七、注意事项密码仅用于表达式求值password只在client_attrs_init渲染阶段进入上下文除非表达式显式将其写入结果否则密码不会存入client_attrs也不会被持久化到属性中。jwt_value 不做签名校验用它做属性预提取是安全的但完整的 JWT 认证应交给 JWT 认证器如emqx_auth_jwt在认证阶段完成。单行表达式限制expression必须是单行表达式支持函数嵌套调用与直接变量引用不支持用户自定义变量绑定或自定义函数参见 rel/i18n/emqx_schema.hocon 的说明。属性名受校验set_as_attr通过restricted_string校验异常或含控制字符的渲染结果会被丢弃避免下游模板注入风险。参考变更记录changes/ee/fix-16684.en.md核心实现apps/emqx/src/emqx_channel.erl配置 schemaapps/emqx/src/emqx_schema.erl内置函数 jwt_valueapps/emqx_utils/src/emqx_variform_bif.erl配置项说明rel/i18n/emqx_schema.hocon相关测试apps/emqx/test/emqx_channel_SUITE.erl、apps/emqx_utils/test/emqx_variform_bif_tests.erl【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址: https://gitcode.com/gh_mirrors/em/emqx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表