news 2026/9/21 2:41:03

Apache APISIX jwe-decrypt 插件实战:JWE 令牌解密与明文透传指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Apache APISIX jwe-decrypt 插件实战:JWE 令牌解密与明文透传指南

Apache APISIX jwe-decrypt 插件实战:JWE 令牌解密与明文透传指南

【免费下载链接】apisixThe Cloud-Native API Gateway and AI Gateway项目地址: https://gitcode.com/gh_mirrors/api/apisix

jwe-decrypt是 Apache APISIX 内置的认证(auth)类型插件,它从请求头中读取符合 JWE Compact Serialization 格式的五段式令牌,依据令牌kid字段匹配 Consumer,使用 AES-256-GCM 解密密文,并在转发到上游(Upstream)前将明文写入指定请求头。本文以插件官方文档为主体,结合 jwe-decrypt.lua 源码与 jwe-decrypt.t 测试用例,系统讲解令牌格式、Consumer 与 Route 配置、完整操作步骤及底层实现原理,帮助你在 Route 或 Service 上快速落地“密文入网关、明文出上游”的令牌解密链路。

插件工作原理与适用场景

jwe-decrypt的核心处理流程如下:

  1. 从请求头(默认Authorization)读取令牌;
  2. 解析令牌的 JOSE Header,提取其中的kid
  3. kidjwe-decrypt插件的 Consumer 配置中查找对应的凭据(Credential);
  4. 以该 Consumer 配置的 32 字节密钥,对密文执行 AES-256-GCM 解密;
  5. 将解密得到的明文写入forward_header指定的请求头,再继续代理请求。

该插件适用于在网关侧统一对上游隐藏 JWE 密文、向上游暴露明文的场景,例如客户端携带 JWE 加密的用户标识或业务载荷访问 API,由网关解密后透传给后端服务消费。你可以在 APISIX 的 Routes 或 Services 上启用该插件。

从源码结构看(apisix/plugins/jwe-decrypt.lua),该插件定义如下:

元信息字段说明
version0.1插件版本
priority2509插件执行优先级
typeauth认证类型插件
namejwe-decrypt插件名称
schema路由/服务侧 schema见下文 Route/Service 属性
consumer_schemaConsumer 侧 schema见下文 Consumer 属性

priority = 2509意味着它在同类认证插件中拥有较高的执行优先级,会在代理阶段较早介入请求处理;其核心解密逻辑实现在rewrite阶段(jwe-decrypt.lua)。

令牌格式:JWE Compact Serialization

jwe-decrypt接受的令牌使用 JWE Compact Serialization(RFC 7516)五段式结构,由点号(.)分隔:

base64url(header).<empty>.base64url(iv).base64url(ciphertext).base64url(tag)

各部分含义:

| 段 | 内容 | 说明 | | -- | ---- | ---- | | 第 1 段 | JOSE Header | 以 base64url 编码的 JSON 对象,包含algenckid| | 第 2 段 | 加密密钥(Encrypted Key) | 使用dir直接加密算法时为空段 | | 第 3 段 | IV | 初始化向量,base64url 编码 | | 第 4 段 | 密文 | 加密后的载荷,base64url 编码 | | 第 5 段 | 认证标签 | AES-GCM 的认证标签,base64url 编码 |

插件只实现dir密钥管理算法与A256GCM内容加密算法,因此使用标准 JWE 库生成、由dir+A256GCM组合产生的令牌均可被接受。algenc被设置为其他值时会被直接拒绝(见下文“算法与格式校验”)。

头部结构

令牌的 JOSE Header 需包含:

{"alg":"dir","enc":"A256GCM","kid":"<consumer-key>"}
  • alg:必须为dir,表示直接使用对称密钥加密;
  • enc:必须为A256GCM,表示 AES-256-GCM 内容加密;
  • kid:Consumer 凭据的key,插件据此定位解密密钥。

IV 必须是每次令牌生成时唯一且随机产生的,严禁在同一个密钥下复用 IV,否则会破坏 AES-GCM 的认证安全性。

配置属性说明

Consumer 侧属性

在 Consumer 上启用jwe-decrypt时,可配置以下属性:

名称类型必填默认值合法值描述
keystring标识 Consumer 凭据的唯一键,对应令牌头中的kid
secretstring32 字节共享对称密钥。建议使用 secret 引用,如$env://...$secret://...
is_base64_encodedbooleanfalse若 secret 为 base64url 编码则设为 true;解码后的字节数仍必须为 32

Route / Service 侧属性

在 Route 或 Service 上启用jwe-decrypt时,可配置以下属性:

名称类型必填默认值合法值描述
headerstringAuthorization从中读取令牌的请求头
forward_headerstringAuthorization将明文传给上游的请求头名称
strictbooleantrue为 true 时,请求缺少 JWE 令牌则返回 403 错误;为 false 时,未找到令牌则放行继续

从 schema 定义看(jwe-decrypt.lua),headerforward_header为必填项,strict默认true;Consumer 侧keysecret必填,且两者均被声明为encrypt_fields,即开启了字段加密后,密钥与 secret 会在写入 etcd 时被加密存储,避免明文落盘。

secret 长度校验的实现细节

Consumer 配置中的secret要求为 32 字节,因为 A256GCM 使用的正是 256 位(32 字节)密钥。源码中的校验逻辑(jwe-decrypt.lua)值得注意:

  • is_base64_encoded为 false,则要求secret字符串长度恰好为 32;
  • is_base64_encoded为 true,则先做 base64url 解码,解码后的字节数也必须恰好为 32,且 secret 本身必须是合法的 base64url 字符串;
  • data_encryption.enable_encrypt_fields开启且配置存储于 etcd 时,secret 在落库前已被加密,密文长度会超过 32,因此跳过长度校验。

该行为在 t/plugin/jwe-decrypt.t 中有完整覆盖:TEST 4 验证普通 secret 超长被拒(the secret length should be 32 chars),TEST 5 验证 base64 编码 secret 解码后超长被拒,TEST 6 验证非法 base64url 字符串被拒。

安全注意事项

向后兼容的 AAD 警告

根据 RFC 7516 的要求,标准 JWE 库会将编码后的受保护头部作为 AES-GCM 的附加认证数据(AAD)进行认证,这使得令牌的kid具备防篡改性——任何对头部的修改都会导致认证失败。

为向后兼容,插件同时接受一种旧式令牌:其密文在加密时未将受保护头部作为 AAD(APISIX 自身此前生成令牌的方式)。这类令牌的头部(包括kid)不受认证保护。因此:

  • 请使用可信的令牌生成方;
  • 优先使用遵循 RFC 7516 的 JWE 库,确保头部被 AAD 覆盖。

从源码看,解密时插件会先按 RFC 7516 方式将o.header(编码后的头部)作为 AAD 尝试解密,失败后再尝试无 AAD 的兼容路径(jwe-decrypt.lua)。测试用例也系统验证了这一双路径行为:

  • TEST 31:由独立 JWE 库(python cryptography)生成、认证了受保护头部的令牌,解密成功(200);
  • TEST 32:无 AAD 的旧式令牌仍然被接受(200);
  • TEST 33:将 RFC 7516 令牌的kid篡改为持有相同密钥的另一 Consumer 后,因标签不再覆盖头部而解密失败(400),直观印证了头部防篡改能力。

上游传输安全提示

解密后的明文会被写入请求头并转发给上游。需要注意:对于敏感明文,不能仅依赖 HTTPS Upstream——APISIX 对标准 HTTP Upstream 并不校验服务端证书。应通过经过认证、受保护的网络路径转发请求,例如经由会校验上游身份的反向代理或服务网格;同时限制上游的访问范围,并避免记录所配置的转发头。

操作示例一:创建带解密密钥的 Consumer

以下示例演示如何创建携带解密密钥的 Consumer,并为其生成 JWE 令牌。

首先获取 Admin API 密钥并存入环境变量:

admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')

通过 Admin API 创建 Consumer

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \ -H "X-API-KEY: ${admin_key}" \ -d '{ "username": "jack", "plugins": { "jwe-decrypt": { "key": "jack-key", "secret": "key-length-should-be-32-chars123" } } }'

其中key: "jack-key"将作为令牌头部kid的匹配值,secret为 32 字节的共享对称密钥(示例字符串key-length-should-be-32-chars123恰好 32 个字符)。

通过 ADC 创建 Consumer

将配置写入adc.yaml后同步到网关:

consumers: - username: jack plugins: jwe-decrypt: key: jack-key secret: key-length-should-be-32-chars123
adc sync -f adc.yaml

通过 Ingress 控制器(Gateway API)创建 Consumer

apiVersion: apisix.apache.org/v1alpha1 kind: Consumer metadata: namespace: aic name: jack spec: gatewayRef: name: apisix plugins: - name: jwe-decrypt config: key: jack-key secret: key-length-should-be-32-chars123
kubectl apply -f jwe-consumer-ic.yaml

注意:ApisixConsumer(APISIX Ingress Controller 的 CRD)仅通过authParameter字段支持认证类插件,且jwe-decrypt不在其支持类型之列,因此该场景无法通过 APISIX Ingress Controller 完成,只能使用上面的 Gateway API 方式。

生成 JWE 令牌

为 Consumer 生成 JWE 令牌时,可使用任何支持dir直接加密 +A256GCM的标准 JWE 库,以 Consumer 的secret为密钥。令牌结构为:

base64url(header).<empty>.base64url(iv).base64url(ciphertext).base64url(tag)

其中 header 为{"alg":"dir","enc":"A256GCM","kid":"<consumer-key>"}。如下令牌将载荷{"uid":10000,"uname":"test"}以密钥jack-key及上述 secret 加密:

eyJraWQiOiJqYWNrLWtleSIsImFsZyI6ImRpciIsImVuYyI6IkEyNTZHQ00ifQ..vi29KBCQKcVmPwTT.VToyPMFbq-ZY05MIpntP1N3AmYeq3zELQ0B6iQ.vuTPG2ODc-DjUTjNCzfA2A

注意:若密钥非明文存储而是通过 secret 引用(如$env://...$secret://...)注入,需在生成令牌时使用引用解析后的真实密钥值。

操作示例二:创建 Route 解密 JWE 数据

以下示例演示如何解密上文生成的 JWE 令牌。创建一个启用jwe-decrypt的 Route,解密Authorization请求头:

通过 Admin API 创建 Route

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \ -H "X-API-KEY: ${admin_key}" \ -d '{ "id": "jwe-decrypt-route", "uri": "/anything/jwe", "plugins": { "jwe-decrypt": { "header": "Authorization", "forward_header": "Authorization" } }, "upstream": { "type": "roundrobin", "scheme": "https", "nodes": { "httpbin.org:443": 1 } } }'

通过 ADC 创建 Route

services: - name: jwe-decrypt-service routes: - name: jwe-decrypt-route uris: - /anything/jwe plugins: jwe-decrypt: header: Authorization forward_header: Authorization upstream: type: roundrobin scheme: https nodes: - host: httpbin.org port: 443 weight: 1
adc sync -f adc.yaml

通过 Ingress 控制器(Gateway API)创建 Route

下面的 Gateway API 配置仅使用公共 HTTPBin 与本页展示的非敏感演示载荷。在转发真实解密数据前,应替换为受控的上游,并使用经过认证、受保护的网络路径。APISIX 的 HTTPS Upstream 本身不会校验上游服务端证书,应使用会校验上游身份的代理或服务网格:

apiVersion: v1 kind: Service metadata: namespace: aic name: httpbin-external-domain spec: type: ExternalName externalName: httpbin.org --- apiVersion: apisix.apache.org/v1alpha1 kind: PluginConfig metadata: namespace: aic name: jwe-decrypt-plugin-config spec: plugins: - name: jwe-decrypt config: header: Authorization forward_header: Authorization --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: namespace: aic name: jwe-decrypt-route spec: parentRefs: - name: apisix rules: - matches: - path: type: Exact value: /anything/jwe filters: - type: ExtensionRef extensionRef: group: apisix.apache.org kind: PluginConfig name: jwe-decrypt-plugin-config backendRefs: - name: httpbin-external-domain port: 80
kubectl apply -f jwe-decrypt-ic.yaml

发起带密文的请求

向 Route 发送请求,在Authorization头中携带 JWE 密文:

curl "http://127.0.0.1:9080/anything/jwe" -H 'Authorization: eyJraWQiOiJqYWNrLWtleSIsImFsZyI6ImRpciIsImVuYyI6IkEyNTZHQ00ifQ..vi29KBCQKcVmPwTT.VToyPMFbq-ZY05MIpntP1N3AmYeq3zELQ0B6iQ.vuTPG2ODc-DjUTjNCzfA2A'

响应中Authorization头即为解密后的明文载荷:

{ "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Authorization": "{\"uid\":10000,\"uname\":\"test\"}", "Host": "127.0.0.1", "User-Agent": "curl/8.1.2", "X-Amzn-Trace-Id": "Root=1-6510f2c3-1586ec011a22b5094dbe1896", "X-Forwarded-Host": "127.0.0.1" }, "json": null, "method": "GET", "origin": "127.0.0.1, 119.143.79.94", "url": "http://127.0.0.1/anything/jwe" }

可见明文{"uid":10000,"uname":"test"}被写入了上游请求的Authorization头。测试用例 TEST 22 也验证了同样的效果:上游收到的Authorization头为解密后的明文。

底层处理流程与错误码

令牌提取

插件从conf.header指定的请求头读取令牌(jwe-decrypt.lua)。若令牌以Bearerbearer前缀开头,会自动剥离前缀后再解析。测试用例 TEST 12/13/14 分别验证了带Bearer、不带前缀、带小写bearer三种写法均可正常解密。

算法与格式校验

rewrite阶段的校验顺序(jwe-decrypt.lua)为:

  1. 令牌缺失:未取到令牌时,strict=true返回 403missing JWE token in requeststrict=false则直接放行(TEST 10、TEST 26);
  2. 令牌结构非法:五段式解析失败或 header 解码失败,返回 400JWE token invalid(TEST 11、TEST 15);header 解码后非 JSON 对象(如 JSONnull或标量)同样被拒(TEST 27、TEST 28);
  3. 缺少 kid:返回 400missing kid in JWE token
  4. 不支持的算法algdirencA256GCM时返回 400unsupported alg or enc in JWE token(TEST 34、TEST 35、TEST 37);header 中省略alg/enc的旧令牌仍可兼容接受(TEST 36);
  5. kid 无对应 Consumer:返回 400invalid kid in JWE token
  6. 解密失败:返回 400failed to decrypt JWE token(TEST 24、TEST 30、TEST 33)。

密钥解析与解密

get_secret依据is_base64_encoded决定是否对 secret 做 base64url 解码(jwe-decrypt.lua);解密时先对 IV、密文、标签分别做 base64url 解码,再以resty.aes的 AES-256-GCM 模式执行解密,并优先以编码后的头部作为 AAD,失败后回退到无 AAD 的兼容路径(jwe-decrypt.lua)。

Consumer 查找

get_consumer通过consumer_mod.plugin(plugin_name)consumer_mod.consumers_kv(plugin_name, consumer_conf, "key")构建kid -> Consumer索引并按key精确匹配(jwe-decrypt.lua)。测试用例 TEST 16 验证了删除 Consumer 后,剩余 Consumer 的令牌仍可正常校验,说明索引随配置变更实时生效。

密钥保护:encrypt_fields 与 secret 引用

Consumer schema 中将keysecret声明为encrypt_fields(jwe-decrypt.lua)。当 conf/config.yaml.example 中的deployment.admin.data_encryption.enable_encrypt_fields设为true且配置存储于 etcd 时,这两个字段在写入配置中心前会被自动加密:

data_encryption: # Data encryption settings. enable_encrypt_fields: true # Whether enable encrypt fields specified in `encrypt_fields` in plugin schema. keyring: # This field is used to encrypt the private key of SSL and the `encrypt_fields` # in plugin schema. - qeddd145sfvddff3 # Set the encryption key. A key of length 16 selects AES-128-CBC and a key - edd1c9f0985e76a2 # of length 32 selects AES-256-CBC; any other length is rejected on startup.

测试用例 TEST 7/8 验证了开启加密后,从 etcd 读取到的 Consumer 配置中keysecret均为密文,且原始明文不会出现在日志中。除字段加密外,官方文档也建议通过 secret 引用($env://...$secret://...)管理密钥,避免密钥明文出现在 Admin API 请求与配置文件中。

总结

jwe-decrypt为 APISIX 提供了一条标准化的 JWE 令牌解密链路:五段式 Compact 令牌 +kid驱动的 Consumer 凭据匹配 + AES-256-GCM 解密 + 明文请求头透传。部署时需把握四个要点:

  1. 密钥管理:Consumer 的secret必须是 32 字节,可配合 base64url 编码、encrypt_fields字段加密与 secret 引用三重手段保护;
  2. 令牌生产:优先使用遵循 RFC 7516 的标准 JWE 库生成令牌,使头部进入 AAD 认证范围,避免kid被篡改的风险;
  3. 路由控制strict=true时缺失令牌返回 403,strict=false时放行,可按业务对强制性的要求选择;
  4. 传输安全:明文经请求头转发,敏感场景下应借助代理或服务网格等经过认证的受保护网络路径传输,并避免记录转发头。

如需深入理解实现细节,可继续阅读 apisix/plugins/jwe-decrypt.lua 源码与 t/plugin/jwe-decrypt.t 测试用例(后者覆盖了包括 AAD 兼容、算法拒绝、错误码在内的 30 余个场景),以及配置中心的 data_encryption 配置说明。

【免费下载链接】apisixThe Cloud-Native API Gateway and AI Gateway项目地址: https://gitcode.com/gh_mirrors/api/apisix

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

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

OpenClaw 安装方法补一步:模型通道改到 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/21 2:37:54

STM32CubeMX安装深度指南:嵌入式AI编程的基座构建

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/21 2:35:19

Sails 应用优雅关闭指南:sails.lower() 方法深度解析

Sails 应用优雅关闭指南&#xff1a;sails.lower() 方法深度解析 【免费下载链接】sails Realtime MVC Framework for Node.js 项目地址: https://gitcode.com/gh_mirrors/sa/sails lower() 是 Sails 生命周期中与 lift() 对应的逆操作&#xff1a;它会关闭已启动的应用…

作者头像 李华
网站建设 2026/9/21 2:33:54

NVIDIA RAG示例工程拆解:从625个文件看工业化范式

说实话&#xff0c;RAG这个概念火到现在&#xff0c;真正能在生产环境里扛住流量、稳定跑上几个月的项目&#xff0c;远没有社区里讨论的那么多。大部分人卡在同一个地方&#xff1a;demo跑得通&#xff0c;一上规模就露馅。我在做企业知识库落地的过程中&#xff0c;也反复经历…

作者头像 李华
网站建设 2026/9/21 2:33:48

AI编程工作流重构:TRAE+Cursor+Windsurf协同实践

1. 这不是“用AI写代码”&#xff0c;而是重构个人开发工作流我从2023年夏天开始系统性地把AI编程工具嵌入日常开发节奏&#xff0c;不是为了炫技&#xff0c;也不是想替代自己写代码的能力&#xff0c;而是解决一个非常具体、非常现实的问题&#xff1a;单人维护3个主力项目2个…

作者头像 李华
网站建设 2026/9/21 2:33:00

降AI率实战指南:从检测原理到改写工具与学术写作方法

上个月有个学弟拿着一张检测报告来找我&#xff0c;整个人快崩溃了&#xff1a;查重过了&#xff0c;AI疑似度却显示28%&#xff0c;而那篇论文确实是他一个字一个字改出来的。这不是个别现象&#xff0c;很多学校现在把AIGC检测和查重并列&#xff0c;甚至卡得更严。于是“降A…

作者头像 李华