ARTICLE DETAIL

资讯详情

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

Apache APISIX jwe-decrypt 插件实战:基于 JWE(RFC 7516)的请求头解密与安全认证

Apache APISIX jwe-decrypt 插件实战:基于 JWE(RFC 7516)的请求头解密与安全认证 Apache APISIX jwe-decrypt 插件实战基于 JWERFC 7516的请求头解密与安全认证【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisixjwe-decrypt是 Apache APISIX 提供的一个认证auth类型插件用于在网关侧解密请求中携带的 JWEJSON Web EncryptionRFC 7516授权头并将解密后的明文转发给上游服务。本文以 docs/en/latest/plugins/jwe-decrypt.md 为骨架结合 jwe-decrypt 插件源码 与 jwe-decrypt 测试用例完整讲解其在 Consumer 与 Route 上的配置参数、加解密端点、密钥管理含 base64 与加密字段、以及删除插件的操作帮助读者把客户端加密、网关解密、上游拿明文的链路一次性落地。背景为什么网关需要 JWE 解密JWERFC 7516描述了一种对 JSON 载荷进行加密的标准化格式与仅做签名的 JWS 不同JWE 保证的是机密性——载荷内容对非授权方不可读。在微服务/网关架构中客户端把敏感数据用户 ID、会话信息等加密后放进 HTTP 头网关解出明文再转交给上游是一种常见的端到端凭证保护方案。jwe-decrypt插件承担了网关侧的解密职责同时内置一个加密端点形成一个闭环加密侧插件提供内部端点/apisix/plugin/jwe/encrypt配合public-api插件暴露出去用 Consumer 里配置的密钥把明文载荷加密成 JWE Token。解密侧请求到达 Route 时插件从指定请求头中取出 JWE Token根据 Token 头部的kid定位 Consumer 密钥解密后把明文写回指定请求头再继续转发给上游。从源码看插件在 apisix/plugins/jwe-decrypt.lua 中声明了version 0.1、priority 2509、type auth即它在认证插件中拥有较高的执行优先级会在路由匹配之后、上游转发之前完成解密动作。插件属性Attributesjwe-decrypt的属性分为两组一组配置在Consumer解密密钥另一组配置在Route解密行为。Consumer 侧属性名称类型必填默认值说明keystring是-Consumer 的唯一标识同时也是 JWE Token 中kid对应的取值。secretstring是-解密密钥必须为 32 个字符。可通过 Secret 资源 将密钥存放到密钥管理器中。is_base64_encodedboolean否false若为 true表示secret是 base64 编码的插件会先解码再使用。注意启用is_base64_encoded后secret的原始长度可以超过 32 字符只需保证解码后的长度仍为 32 字符即可。这一32 字符的硬约束来自底层加密算法源码在 apisix/plugins/jwe-decrypt.lua 使用aes.cipher(256, gcm)创建 AES-256-GCM 密码对象256 位即 32 字节。check_schema在 Consumer 模式下会严格校验长度apisix/plugins/jwe-decrypt.lua未开启is_base64_encoded时要求#conf.secret 32否则报错the secret length should be 32 chars开启后要求#base64.decode_base64url(conf.secret) 32否则报错the secret length after base64 decode should be 32 chars。对应地jwe-decrypt 测试用例 的 TEST 4、TEST 5 分别验证了这两种报错分支TEST 1924 则验证了 base64 密钥的完整加解密链路。另外Consumer schema 中声明了encrypt_fields { key, secret }apisix/plugins/jwe-decrypt.lua表示这两个字段属于可加密敏感字段。当在 conf/config.yaml 中启用apisix.data_encryption.enable_encrypt_fields且配置中心为 etcd 时存入 etcd 的key/secret会被自动加密存储源码 apisix/plugins/jwe-decrypt.lua 会在该场景下跳过长度校验因为密文长度必然超过 32 字符测试 t/plugin/jwe-decrypt.t 的 TEST 7 直接从 etcd 读取到加密后的字段值作为佐证。Route 侧属性名称类型必填默认值说明headerstring是Authorization从哪个请求头中读取 JWE Token。forward_headerstring是Authorization解密后的明文写入哪个请求头再转发给上游。strictboolean否true为 true 时若请求中缺失 JWE Token 则直接返回 403为 false 时找不到 Token 不报错请求继续放行。需要说明的是官方文档将header/forward_header标为必填Required True而源码 apisix/plugins/jwe-decrypt.lua 的 schema 为两者提供了默认值Authorization并列入required实际使用时即使不显式配置也会按Authorization头处理。strict的默认值true与源码default true完全一致。strict的行为在 rewrite 阶段 体现fetch_jwe_token取不到 Token 且strict为 true 时返回403 {message:missing JWE token in request}测试 t/plugin/jwe-decrypt.t 的 TEST 12 精确断言了这一响应。加解密流程与源码级原理jwe-decrypt的解密逻辑全部发生在rewrite阶段apisix/plugins/jwe-decrypt.lua完整链路如下取 Tokenfetch_jwe_token读取conf.header指定的请求头若 Token 以Bearer或bearer前缀开头则自动去掉前缀apisix/plugins/jwe-decrypt.lua。测试 TEST 14/15/16 验证了带 Bearer、不带 Bearer、小写 bearer 三种写法均可正常解密。解析 JWEload_jwe_token按 JWE 紧凑序列化格式header.enckey.iv.ciphertext.tag切分五段并对 header 做 base64url 解码与 JSON 解析apisix/plugins/jwe-decrypt.lua解析失败返回 400JWE token invalidTEST 13/17。定位密钥校验 JWE header 中的kid必须存在并通过get_consumer(kid)在配置了jwe-decrypt插件的 Consumer 中按key字段精确匹配apisix/plugins/jwe-decrypt.luakid缺失返回 400missing kid in JWE token找不到对应 Consumer 返回 400invalid kid in JWE token。这意味着密钥分发以kid为索引一个 Consumer 对应一把密钥。解密jwe_decrypt_with_obj用get_secret取出密钥base64 场景先解码以 JWE 的 IV 初始化 AES-256-GCM 后解密 ciphertext 并校验 tagapisix/plugins/jwe-decrypt.lua失败返回 400failed to decrypt JWE token。回写明文core.request.set_header(ctx, conf.forward_header, plaintext)把解密出的明文写入forward_header指定的请求头随后请求带明文继续转发给上游。测试 TEST 26 通过 httpbin 上游断言了上游收到的Authorization头正是明文hellot/plugin/jwe-decrypt.t。加密端由插件声明的插件级 API 提供_M.api()返回一个 GET 端点/apisix/plugin/jwe/encryptapisix/plugins/jwe-decrypt.lua。APISIX 启动时会收集所有插件声明的 API 路由见 apisix/api_router.lua 对plugin.api的遍历因此该端点默认只在内部 API 路由中存在需配合public-api插件显式暴露到公网。public-api在 access 阶段 将请求 URI 覆盖为目标 URI 并在内部 API 路由中匹配从而实现内部端点对外发布。gen_tokenapisix/plugins/jwe-decrypt.lua的加密实现细节必填 URI 参数key定位 Consumer 密钥与payload要加密的明文缺key返回 400找不到 Consumer 返回 404可选参数iv指定初始化向量不传时使用固定值123456789012源码中标注为 TODO 随机字节生产环境建议自行传入随机 IV生成 JWE header{kid: key, alg: dir, enc: A256GCM}即直接使用对称密钥dir 模式配合 AES-256-GCM输出header......base64url(iv).....base64url(ciphertext).....base64url(tag)即紧凑序列化的 JWE Token。使用示例从加密到解密的完整闭环下面按照官方文档 docs/en/latest/plugins/jwe-decrypt.md 的操作顺序演示完整链路。第一步准备 admin_keyAdmin API 的调用需要携带管理员密钥可先从 conf/config.yaml 中读取并保存到环境变量该文件在deployment.admin.admin_key下配置管理员凭证生产环境务必替换默认密钥admin_key$(yq .deployment.admin.admin_key[0].key conf/config.yaml | sed s///g)第二步创建带解密密钥的 Consumerjwe-decrypt的密钥必须配置在 Consumer 上key字段即 JWE Token 的kidcurl http://127.0.0.1:9180/apisix/admin/consumers -H X-API-KEY: $admin_key -X PUT -d { username: jack, plugins: { jwe-decrypt: { key: user-key, secret: -secret-length-must-be-32-chars- } } }注意secret必须是 32 个字符上述示例字符串恰好 32 字符。若密钥以 base64 形式存储需同时设置is_base64_encoded: true例如测试 t/plugin/jwe-decrypt.t 中使用的fo4XKdZ1xSrIZyms4q2BwPrW5lMpls9qqy5tiAk2esc。第三步在 Route 上启用解密创建一个启用jwe-decrypt的 Route此处插件配置留空即使用Authorization头作为输入与输出curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: $admin_key -X PUT -d { methods: [GET], uri: /anything*, plugins: { jwe-decrypt: {} }, upstream: { type: roundrobin, nodes: { httpbin.org:80: 1 } } }若希望从自定义请求头读取 Token、并把明文写入另一个头可显式配置header与forward_header例如jwe-decrypt: {header: X-JWE, forward_header: X-Plain}。第四步暴露加密端点并生成 JWE Token插件自带的加密端点默认不对外需要创建一条挂载public-api插件的 Route 来暴露curl http://127.0.0.1:9180/apisix/admin/routes/jwenew -H X-API-KEY: $admin_key -X PUT -d { uri: /apisix/plugin/jwe/encrypt, plugins: { public-api: {} } }然后通过 URI 参数keyConsumer 的 key和payload要加密的明文调用加密接口注意 payload 需 URL 编码curl -G --data-urlencode payload{uid:10000,uname:test} http://127.0.0.1:9080/apisix/plugin/jwe/encrypt?keyuser-key -i响应体即为紧凑序列化的 JWE Token响应头中的Apisix-Plugins: public-api表明请求由 public-api 插件处理HTTP/1.1 200 OK Date: Mon, 25 Sep 2023 02:38:16 GMT Content-Type: text/plain; charsetutf-8 Transfer-Encoding: chunked Connection: keep-alive Server: APISIX/3.5.0 Apisix-Plugins: public-api eyJhbGciOiJkaXIiLCJraWQiOiJ1c2VyLWtleSIsImVuYyI6IkEyNTZHQ00ifQ..MTIzNDU2Nzg5MDEy.hfzMJ0YfmbMcJ0ojgv4PYAHxPjlgMivmv35MiA.7nilnBt2dxLR_O6kf-HQUA对该 Token 做 base64url 解码其 header 即可验证{alg:dir,kid:user-key,enc:A256GCM}kid与 Consumer 的key一一对应。第五步携带 JWE Token 访问 Route 完成解密把上一步得到的 Token 放进Authorization头请求 Routecurl http://127.0.0.1:9080/anything/hello -H Authorization: eyJhbGciOiJkaXIiLCJraWQiOiJ1c2VyLWtleSIsImVuYyI6IkEyNTZHQ00ifQ..MTIzNDU2Nzg5MDEy.hfzMJ0YfmbMcJ0ojgv4PYAHxPjlgMivmv35MiA.7nilnBt2dxLR_O6kf-HQUA -i从响应可以看到上游 httpbin 收到的Authorization头已经是解密后的明文 JSON响应头Apisix-Plugins: jwe-decrypt标明插件已生效HTTP/1.1 200 OK Content-Type: application/json Content-Length: 452 Connection: keep-alive Date: Mon, 25 Sep 2023 02:38:59 GMT Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true Server: APISIX/3.5.0 Apisix-Plugins: jwe-decrypt { 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: Root1-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/hello }常见错误响应速查结合 jwe-decrypt 测试用例 与源码可将各错误分支整理如下便于排障触发条件响应请求缺失 JWE Token 且strict true403 {message:missing JWE token in request}Token 不符合 JWE 紧凑格式 / header 无法解析400 {message:JWE token invalid}JWE header 中缺少kid400 {message:missing kid in JWE token}kid找不到对应 Consumer400 {message:invalid kid in JWE token}解密失败密钥错误、密文被篡改等400 {message:failed to decrypt JWE token}加密端点缺失key参数400加密端点key找不到 Consumer404非 GET 方法访问加密端点404见 TEST 11Consumersecret非 32 字符未加密存储时schema 校验失败the secret length should be 32 chars删除插件要移除jwe-decrypt插件只需把 Route 配置中plugins字段里的对应 JSON 配置删除APISIX 会自动热加载无需重启curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: $admin_key -X PUT -d { methods: [GET], uri: /anything*, plugins: {}, upstream: { type: roundrobin, nodes: { httpbin.org:80: 1 } } }删除后Route 不再执行 JWE 解密Authorization头会原样透传给上游。同理删除 Consumer 上配置的jwe-decrypt插件即可撤销对应密钥测试 t/plugin/jwe-decrypt.t 的 TEST 18 演示了删除 Consumer 后加密端点对已删除 key 的行为变化。注意事项与最佳实践密钥长度与算法绑定插件固定使用 AES-256-GCMalgdir因此密钥必须是解码后 32 字节256 位这是校验逻辑的硬性前提。kid即密钥索引JWE header 的kid必须与 Consumer 的key精确匹配多租户场景下应为每个 Consumer 配置独立密钥。密钥托管建议通过 Secret 资源 将secret存放到外部密钥管理器同时可在 conf/config.yaml 中开启apisix.data_encryption.enable_encrypt_fields让 etcd 中存储的key/secret自动加密开启后长度校验自动跳过因为密文必然超过 32 字符。strict模式取舍生产环境建议保持strict true防止未携带 Token 的请求被静默放行若插件仅用于能解就解、解不了不拦截的场景再考虑false。IV 的随机性加密端点的iv参数不传时使用固定值源码中标注为待改进项涉密场景建议每次调用传入随机 IV同时加密端点一经public-api暴露即公开可访问请结合访问控制如 ip-restriction 插件限制调用来源。前后端协议一致性解密后的明文会覆盖写入forward_header上游需按约定解析该头若header与forward_header相同原始 JWE Token 将被明文替换避免密文泄露给上游。【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表