Envoy 限流描述符扩展详解:从 JWT 中提取 Claim 构建 Rate Limit Descriptor
2026/9/12 17:15:29 网站建设 项目流程

Envoy 限流描述符扩展详解:从 JWT 中提取 Claim 构建 Rate Limit Descriptor

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

导读

本篇文章围绕 Envoy 新增的限流描述符扩展envoy.rate_limit_descriptors.jwt_claim展开,讲解如何在路由级限流(Rate Limit)配置中,直接从 HTTP 请求头或 Cookie 中携带的 JWT 提取指定 claim(如subiss)作为限流描述符(descriptor)的值,从而按用户/租户维度做精细化限流。读完本文你将掌握该扩展的完整配置字段、取值优先级逻辑、与jwt_authn过滤器配合的安全边界,以及对应的底层源码实现与测试验证。

一、扩展背景:为什么需要从 JWT 提取限流描述符

Envoy 的全局速率限制(global rate limiting)通过路由配置中的RateLimit规则生成描述符(descriptor),再交给外部的限流服务(如 gRPC 限流服务)进行计数与判定。传统的限流动作(action)包括source_clusterdestination_clusterrequest_headersremote_addressgeneric_key等,定义在 route_components.proto 的RateLimit.Action中。

对于"按用户限流"这类场景,最自然的做法是把用户身份信息(如 JWT 中的subissclaim)拼进描述符。但在该扩展出现之前,要从 JWT 中取 claim 值,通常需要借助jwt_authn过滤器把 claim 写入 header 或动态元数据,再通过request_headersmetadata描述符动作间接实现。

envoy.rate_limit_descriptors.jwt_claim扩展提供了一条更直接的路径:无需任何额外过滤器,直接在限流描述符动作中指定要读取的 header/cookie、要提取的 claim 名,Envoy 解析 JWT 后把 claim 值作为描述符值。根据官方变更记录(changelog 条目),这一能力尤其适合 JWT 校验由其他组件完成的场景——例如应用自身校验,或上游 mTLS 认证服务校验——而 Envoy 只需要基于 claim 值做限流。

二、扩展与配置模型

2.1 扩展标识

该扩展的完整名称(factory name)为:

envoy.rate_limit_descriptors.jwt_claim

其配置类型为envoy.extensions.rate_limit_descriptors.jwt_claim.v3.Descriptor,定义于 jwt_claim.proto,属于envoy.rate_limit_descriptors扩展类别。限流描述符扩展通过RateLimit.Action中的extension字段(core.v3.TypedExtensionConfig)接入,见 route_components.proto 的说明。

2.2 注册与实现结构

从源码结构看,该扩展由三部分组成:

  • 工厂与实现类:config.h、config.cc;
  • Bazel 构建目标:BUILD(envoy_cc_extension,依赖//source/common/jwt:jwt_lib//envoy/ratelimit:ratelimit_interface等);
  • 单元测试:config_test.cc。

工厂类JwtClaimDescriptorFactory继承RateLimit::DescriptorProducerFactory,通过REGISTER_FACTORY宏注册,name()返回envoy.rate_limit_descriptors.jwt_claim(见 config.cc)。

三、配置字段详解

Descriptor消息共定义 7 个字段(proto 中预留至第 8 号字段),逐一说明如下(字段约束均来自 jwt_claim.proto):

字段类型必填说明
descriptor_keystring描述符条目中的 key,最小长度 1
header_namestring二选一携带 JWT 的 HTTP 头名,如authorization;必须符合 HTTP header 名规范
cookiestring二选一携带 JWT 的 Cookie 名,如auth_token;从请求的Cookie头中查找
value_prefixstring解析前需要剥离的值前缀,如"Bearer "(含末尾空格),用于Authorization: Bearer <token>场景
claim_namestring要提取的 JWT claim 名,支持点号分隔的嵌套 claim,如claim.nested.key;最小长度 1
default_valuestring兜底值:当 header/cookie 缺失、JWT 结构无效或 claim 缺失/非字符串时使用
skip_if_absentbool为 true 时,在上述缺失场景且未配置default_value的情况下,跳过该描述符条目而非中止整个描述符;默认 false

关键约束header_namecookie必须且只能设置一个。该约束由 proto 校验规则配合工厂层双重保证——在 config.cc 中,createDescriptorProducerFromProto会显式检查config.header_name().empty() == config.cookie().empty(),若两者同时为空或同时非空,直接返回错误"jwt_claim descriptor: exactly one of header_name or cookie must be set"

3.1 最小可运行配置示例

actions: - extension: name: jwt_claim_descriptor typed_config: "@type": type.googleapis.com/envoy.extensions.rate_limit_descriptors.jwt_claim.v3.Descriptor descriptor_key: my_descriptor_name header_name: authorization value_prefix: "Bearer " claim_name: sub

该配置生成形如("my_descriptor_name", "<sub claim 值>")的描述符条目(描述符条目的形式定义见 jwt_claim.proto 中的 code-block)。这正是单元测试ExtractsSimpleClaim验证的用例(见 config_test.cc)。

3.2 从 Cookie 提取

actions: - extension: name: jwt_claim_descriptor typed_config: "@type": type.googleapis.com/envoy.extensions.rate_limit_descriptors.jwt_claim.v3.Descriptor descriptor_key: my_descriptor_name cookie: auth_token claim_name: sub

对应测试ExtractsClaimFromCookie:请求Cookie: other=1; auth_token=<jwt>; more=2时,扩展通过Http::Utility::parseCookieValue精确解析出auth_token的值(见 config_test.cc 与 config.cc)。

四、取值优先级与行为规则

从实现JwtClaimDescriptor::populateDescriptor(config.cc)可以梳理出完整的行为链:

尝试提取 claim 值 ├── 成功 → 生成 (descriptor_key, claim_value),返回 true └── 失败(header/cookie 缺失、前缀不匹配、JWT 解析失败、claim 缺失或非字符串) ├── default_value 非空 → 生成 (descriptor_key, default_value),返回 true └── default_value 为空 ├── skip_if_absent = true → 跳过该条目,返回 true(不中止整个描述符) └── skip_if_absent = false → 返回 false(中止整个描述符,不向限流服务发送)

4.1 提取步骤的底层实现

extractClaimValue(config.cc)按顺序执行:

  1. 取原始值:配置了cookie则调用Http::Utility::parseCookieValue解析;否则读取 header(取第一个匹配值)。空值视为缺失。
  2. 剥离前缀:若配置了value_prefix,用absl::StartsWith检查原始值是否以该前缀开头;不匹配则视为缺失(不会尝试解析)。匹配则移除前缀。这解释了为何 cookie 场景通常不需要配置前缀(测试ExtractsClaimFromCookieWithValuePrefix也验证了 cookie + 前缀的组合行为)。
  3. 解析 JWT:调用JwtVerify::Jwt::parseFromString(来自//source/common/jwt公共库),解析失败(非结构合法 JWT)视为缺失。
  4. 提取嵌套 claim:通过JwtVerify::StructUtils::GetValue按点号分隔路径查找 claim,例如nested.key对应 JSON 中的{"nested":{"key":...}}
  5. 字符串化claimValueAsString仅接受字符串类型的 claim 值;数字、布尔、数组、对象等一律按"缺失"处理(config.cc)。空字符串 claim 会被视为"存在",生成值为空的描述符条目(测试ExtractsEmptyStringClaim验证了这一点,即使skip_if_absent: true也不影响空字符串 claim 生成条目)。

4.2 行为规则速查表

场景结果
header/cookie 缺失,无 default_value无 default_value 时中止整个描述符(或 skip 单条目)
值前缀不匹配视为缺失,同上
JWT 结构非法(如not-a-jwt视为缺失,同上
claim 缺失或非字符串视为缺失,同上
claim 值为空字符串生成(key, "")条目(总是"存在")
claim 值正常(字符串)生成(key, value)条目

五、安全边界:务必先完成 JWT 签名校验

这是该扩展最重要的一条使用红线(jwt_claim.proto 中有完整的安全警告):

该扩展不校验 JWT 签名。任何一方都可以伪造或使用过期 JWT,并随意构造 claim 值,这些值会被直接用作限流描述符值。攻击者可以轻松绕过或操纵基于 claim 的限流——例如冒充其他 subject,或每次请求轮换伪造的 claim 值以完全绕过限流。

测试ForgedJwtStillExtractsUnverifiedClaim专门演示了这一行为:一个任意构造、签名部分无意义的 token,其 claim 值照样被提取(config_test.cc)。测试辅助函数makeJwt也直接以"sig"作为签名段,进一步佐证该扩展不关心签名有效性。

因此官方文档明确要求:

  • 不要用该扩展做访问控制、按信任级别差异化限流,或任何与授权判断相关的决策,除非能证明请求路径上的其他环节已经完成了签名校验;
  • 推荐做法:如果需要 Envoy 自己先验证 JWT 再限流,应把envoy.filters.http.jwt_authn过滤器放在限流过滤器之前,并通过request_headersmetadata描述符动作(而非本扩展)来构建描述符。jwt_authn过滤器在拒绝非法 token 后,本扩展(或描述符动作)才会执行。

典型的合法部署形态是:JWT 校验由应用或上游 mTLS 认证服务完成,Envoy 侧的 jwt_claim 扩展只负责"读取已可信的 claim 值用于限流"。

六、配置文件校验与常见错误

工厂在配置加载时会进行 proto 校验(MessageUtil::downcastAndValidate),两类常见错误会直接导致配置加载失败(对应测试见 config_test.cc):

  1. 同时设置header_namecookie→ 报错jwt_claim descriptor: exactly one of header_name or cookie must be set
  2. 两者都未设置→ 同样的报错。

此外descriptor_keyclaim_name为必填(min_len: 1),header_name/cookie/value_prefix需符合 HTTP header 名/值规范。

七、测试覆盖一览

该扩展的单元测试覆盖了绝大多数行为分支,可作为理解语义的权威参考(config_test.cc):

  • ExtractsSimpleClaim:普通字符串 claim 提取;
  • ExtractsEmptyStringClaim:空字符串 claim 生成空值条目;
  • ExtractsNestedClaim:嵌套 claim(nested.key);
  • MissingHeaderNoDefaultAbortsDescriptor:缺失且无兜底时中止描述符;
  • MissingHeaderWithSkipIfAbsentSkipsDescriptorskip_if_absent: true时跳过单条目,不影响同 action 列表中其他条目;
  • DefaultValueUsedWhenClaimAbsent/DefaultValueUsedWhenClaimIsAbsentFromValidJwt/MalformedJwtUsesDefaultValuedefault_value兜底分支;
  • NonStringCustomClaimTreatedAsAbsent:数字 claim 按缺失处理;
  • DefaultValueUsedWhenNestedClaimHasScalarIntermediateValue:嵌套路径中间节点为标量(非对象)时按缺失处理;
  • ValuePrefixMismatchTreatedAsAbsent/NoValuePrefixConfigured:前缀匹配语义;
  • ExtractsClaimFromCookie/ExtractsClaimFromCookieWithValuePrefix:Cookie 来源提取;
  • RejectsConfigWithBothHeaderAndCookie/RejectsConfigWithNeitherHeaderNorCookie:配置校验;
  • ForgedJwtStillExtractsUnverifiedClaim:不校验签名的事实佐证。

八、总结

envoy.rate_limit_descriptors.jwt_claim为 Envoy 的全局限流体系补齐了"直接从 JWT 取 claim 作为描述符值"的能力,配置简洁、无额外过滤器依赖,特别适合 JWT 校验已由外部组件完成的架构。使用时务必牢记其安全边界:它只做结构解析,不做签名验证——将签名校验前置到jwt_authn过滤器或可信上游服务,才能安全地基于 claim 值实施按用户/租户维度的限流策略。

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询