
Apache APISIX hmac-auth 插件实战HMAC 签名认证的配置、签名计算与源码解析【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisixhmac-auth是 Apache APISIX 内置的认证类插件通过 HMAC 签名机制为 Route 或 Service 提供请求身份校验适用于服务间调用鉴权、开放 API 接入方认证等场景。本文基于 hmac-auth 官方文档 与该插件在仓库中的真实实现 apisix/plugins/hmac-auth.lua系统讲解插件属性、启用方式、签名生成公式、请求体校验、自定义认证请求头并结合源码与测试用例t/plugin/hmac-auth.t说明其底层校验流程。读完本文你将能够在 APISIX 中独立配置 hmac-auth 消费者、计算出正确的 HMAC 签名并掌握多语言签名生成与排错方法。插件概述基于 Consumer 的 HMAC 认证hmac-auth插件为 Route 或 Service 添加 HMAC 认证能力。它依赖 Consumer 对象工作API 的调用方Consumer必须把其密钥放入请求头中供网关校验。相比简单的 key-authHMAC 认证将请求方法、URI、查询参数、时间戳与指定请求头一起纳入签名计算能够有效防止请求被篡改与重放。在源码 apisix/plugins/hmac-auth.lua 中可以看到该插件的基本元信息local _M { version 0.1, priority 2530, type auth, name plugin_name, schema schema, consumer_schema consumer_schema }priority 2530意味着它在认证阶段具有较高的执行优先级type auth表明它属于认证类插件通过rewrite阶段apisix/plugins/hmac-auth.lua完成校验并在失败时直接返回401与{message:client request cant be validated}。插件属性Attributes详解以下属性均配置在Consumer 对象的hmac-auth插件配置中名称类型必填默认值合法值描述access_keystring是--Consumer 的唯一标识。若不同 Consumer 配置了相同 key会出现请求匹配异常secret_keystring是--与access_key配对使用的密钥。该字段支持通过 APISIX Secret 资源保存在 Secret Manager 中algorithmstring否hmac-sha256[hmac-sha1, hmac-sha256, hmac-sha512]签名使用的加密算法clock_skewinteger否0-签名允许的时钟偏移秒。设为0时跳过日期校验signed_headersarray[string]否--参与签名计算的请求头列表。指定后客户端请求只能携带这些指定请求头未指定时全部请求头参与计算keep_headersboolean否false[true, false]为true时认证成功后保留请求头X-HMAC-SIGNATURE、X-HMAC-ALGORITHM与X-HMAC-SIGNED-HEADERS否则移除encode_uri_paramsboolean否true[true, false]为true时对 URI 参数做 URL 编码例如params1hello%2Cworld会被编码而params2hello,world不会被编码validate_request_bodyboolean否false[true, false]为true时校验请求体max_req_bodyinteger否512 * 1024-允许的最大请求体大小字节对应地apisix/plugins/hmac-auth.lua 中的consumer_schema给出了更精确的约束细节可作为配置校验的参考access_key、secret_key字符串长度限制 1256signed_headers中的每个请求头名称字符串长度限制 150max_req_body默认值为MAX_REQ_BODY即1024 * 512512KB定义于 apisix/plugins/hmac-auth.luarequired {access_key, secret_key}两个字段缺一不可。加密存储consumer_schema中定义了encrypt_fields {secret_key}意味着该字段在 etcd 中以加密形式存储。关于 APISIX 加密存储字段的机制可参见 plugin-develop.md 中的 encrypted storage fields 章节通过 Admin API 增删改资源时自动加密、读取资源及插件运行时自动解密。此外secret_key也支持通过 APISIX Secret 资源引用外部密钥管理系统的值。启用插件第一步为 Consumer 启用首先在 Consumer 对象上启用插件。文档示例使用 Admin API 创建名为jack的 Consumercurl http://127.0.0.1:9180/apisix/admin/consumers -H X-API-KEY: $admin_key -X PUT -d { username: jack, plugins: { hmac-auth: { access_key: user-key, secret_key: my-secret-key, clock_skew: 0, signed_headers: [User-Agent, Accept-Language, x-custom-a] } } }其中admin_key从conf/config.yaml中提取该文件位于仓库 conf/config.yaml默认配置见 conf/config.yaml.exampleadmin_key$(yq .deployment.admin.admin_key[0].key conf/config.yaml | sed s///g)如果本机没有yq也可以直接打开conf/config.yaml查看deployment.admin.admin_key下的 key 值手动填入环境变量。测试用例 t/plugin/hmac-auth.t 验证了缺失secret_key或缺失access_key时 Admin API 都会返回 400例如{error_msg:invalid plugins configuration: failed to check the configuration of plugin hmac-auth err: property \secret_key\ is required}同时该测试文件还验证了access_key与secret_key的 256 长度上限t/plugin/hmac-auth.t。也可以通过 APISIX Dashboard 的 Web 界面完成上述操作。仓库 docs/assets/images/plugin 目录保存了 Dashboard 操作界面的截图第一步填写 Consumer 基本信息第二步为 Consumer 启用 hmac-auth 插件并配置密钥第二步为 Route 或 Service 绑定插件接下来将插件配置到 Route或 Service上使该路由的所有请求都要求 HMAC 认证curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: $admin_key -X PUT -d { uri: /index.html, plugins: { hmac-auth: {} }, upstream: { type: roundrobin, nodes: { 127.0.0.1:1980: 1 } } }注意 Route 上hmac-auth的配置体为空{}插件的实际认证参数access_key、secret_key 等均定义在 Consumer 上Route 只负责声明“此路由需要 HMAC 认证”。签名生成原理签名公式签名计算公式为signature HMAC-SHAx-HEX(secret_key, signing_string)生成签名需要两个参数secret_key由 Consumer 配置与signing_string待签字符串。其中signing_string HTTP Method \n HTTP URI \n canonical_query_string \n access_key \n Date \n signed_headers_string各组成部分的含义HTTP Method大写的 HTTP 请求方法如 GET、PUT、POSTHTTP URI请求 URI必须以/开头/表示空路径DateHTTP 头中的日期GMT 格式canonical_query_stringURL 中查询字符串问号?之后的key1value1key2value2规范化编码后的结果signed_headers_string参与签名计算的指定请求头拼接结果。缺失项处理上述任何一项缺失时都以空字符串参与计算见 apisix/plugins/hmac-auth.lua 中core.table.concat(signing_string_items, \n) .. \n的实现末尾还会追加一个换行符。canonical_query_string 生成算法从 URL 中提取查询项以为分隔符将查询项拆分为键值对若encode_uri_params为true只有 key 时转换公式为uri_encode(key) 同时存在 key 和 value 时转换公式为uri_encode(key) uri_encode(value)value 可以为空字符串按 key 的字典序排序用连接生成canonical_query_string若encode_uri_params为false只有 key 时转换公式为key 同时存在 key 和 value 时转换公式为key valuevalue 可以为空字符串按 key 的字典序排序用连接生成canonical_query_string。在 apisix/plugins/hmac-auth.lua 的generate_signature函数中可以看到该算法的实际实现query 参数被收集后按键排序再根据encode_uri_params决定是否调用ngx.escape_uri对 key/value 编码。对于 URL 中只出现 key 没有value的情况源码将参数值true替换为空字符串以保持兼容apisix/plugins/hmac-auth.lua。signed_headers_string 生成算法从请求头中取出需要参与计算的指定请求头按name:value格式拼接得到signed_headers_stringHeaderKey1 : HeaderValue1 \n HeaderKey2 : HeaderValue2 \n ... HeaderKeyN : HeaderValueN \n注意name与value之间使用冒号直接连接没有空格。源码中的实现为h .. : .. canonical_headerapisix/plugins/hmac-auth.lua其中canonical_header通过core.request.header(ctx, h)读取缺失时取空字符串。签名生成全流程逐步推导以下请求为例curl -i http://127.0.0.1:9080/index.html?namejamesage36 \ -H X-HMAC-SIGNED-HEADERS: User-Agent;x-custom-a \ -H x-custom-a: test \ -H User-Agent: curl/7.29.0第 1 步HTTP Method请求默认方法为 GETsigning_string为GET第 2 步拼接 HTTP URI请求 URI 为/index.html得到GET /index.html第 3 步拼接 canonical_query_stringURL 中查询项为namejamesage36假设encode_uri_params为false。按 canonical_query_string 算法对 key 做字典排序得到age36namejamesGET /index.html age36namejames第 4 步拼接 access_keyaccess_key为user-keyGET /index.html age36namejames user-key第 5 步拼接 DateDate 为 GMT 格式如Tue, 19 Jan 2021 11:33:20 GMTGET /index.html age36namejames user-key Tue, 19 Jan 2021 11:33:20 GMT第 6 步拼接 signed_headers_stringsigned_headers指定参与签名的请求头示例中为User-Agent: curl/7.29.0与x-custom-a: testGET /index.html age36namejames user-key Tue, 19 Jan 2021 11:33:20 GMT User-Agent:curl/7.29.0 x-custom-a:test 最终将上述字符串作为 message、my-secret-key作为 secret使用 hmac-sha256 计算并做 base64 编码即得到签名。官方文档给出的 Python 生成代码如下import base64 import hashlib import hmac secret bytes(my-secret-key, utf-8) message bytes(GET /index.html age36namejames user-key Tue, 19 Jan 2021 11:33:20 GMT User-Agent:curl/7.29.0 x-custom-a:test , utf-8) hash hmac.new(secret, message, hashlib.sha256) # to lowercase base64 print(base64.b64encode(hash.digest()))计算得到的签名值类型值SIGNATURE8XV1GB7Tq23OJcoz6wjqTs4ZLxr9DiLoY4PxzScWGYg注意示例中的待签字符串末尾包含一个换行符这与源码concat(..., \n) .. \n的实现一致apisix/plugins/hmac-auth.lua。signing_string由六个部分依次以\n连接最后再补一个\n结尾。如果需要其他编程语言Java、Go、Ruby、Node.js、JavaScript ES6、PHP、Lua、Shell的签名生成示例可参考仓库中的 Generating HMAC signatures该文档为每种语言提供了 hex 与 base64 两种输出方式的完整代码。源码中的签名校验流程从源码结构看apisix/plugins/hmac-auth.lua 中的validate函数按以下顺序完成校验检查access_key、signature、algorithm是否存在通过get_consumer(access_key)查找 Consumerapisix/plugins/hmac-auth.lua若 key 无效返回Invalid access key校验请求携带的algorithm与 Consumer 配置的algorithm是否一致否则返回algorithm x not supported若clock_skew 0用ngx.parse_http_time解析 Date 头并计算与网关当前时间的差值超过clock_skew则返回Clock skew exceeded若配置了signed_headers校验请求声明的签名请求头是否都在允许列表中否则返回Invalid signed header x用 Consumer 的secret_key与请求参数重新计算签名与请求携带的 base64 解码后的签名比较不一致返回Invalid signature。测试用例 t/plugin/hmac-auth.t 逐一验证了上述失败分支缺失签名、缺失算法、无效 access key、不支持算法、时钟偏移超限、非法 GMT 时间等场景均返回 401 与{message:client request cant be validated}具体错误原因会写入 error log。校验请求体validate_request_body将validate_request_body设置为true后插件会计算请求体的 HMAC-SHA 值并与X-HMAC-DIGEST请求头比对X-HMAC-DIGEST: base64(hmac-sha(body))如果请求没有请求体可将X-HMAC-DIGEST设置为空字符串的 HMAC-SHA 值。性能提醒计算请求体摘要时插件会把请求体加载到内存中。请求体过大时可能造成较高的内存消耗。可以通过配置max_req_body默认 512KB限制允许的最大请求体大小超过设定大小的请求体将被拒绝。源码中对应的实现位于 apisix/plugins/hmac-auth.lua当validate_request_body为真时通过core.request.get_body(max_req_body, ctx)读取请求体超过限制会返回Exceed body limit size将请求体空时为用hmac_funcs[params.algorithm]计算摘要并 base64 编码与X-HMAC-DIGEST头比对不一致则返回Invalid digest。携带签名发起请求方式一签名放在 X-HMAC-* 独立请求头中curl -i http://127.0.0.1:9080/index.html?namejamesage36 \ -H X-HMAC-SIGNATURE: 8XV1GB7Tq23OJcoz6wjqTs4ZLxr9DiLoY4PxzScWGYg \ -H X-HMAC-ALGORITHM: hmac-sha256 \ -H X-HMAC-ACCESS-KEY: user-key \ -H Date: Tue, 19 Jan 2021 11:33:20 GMT \ -H X-HMAC-SIGNED-HEADERS: User-Agent;x-custom-a \ -H x-custom-a: test \ -H User-Agent: curl/7.29.0认证通过后返回正常响应HTTP/1.1 200 OK Content-Type: text/html; charsetutf-8 Transfer-Encoding: chunked Connection: keep-alive Date: Tue, 19 Jan 2021 11:33:20 GMT Server: APISIX/2.2 ......方式二签名放入 Authorization 头签名也可以放在Authorization请求头中格式为hmac-auth-v1#加#分隔的多个字段curl http://127.0.0.1:9080/index.html \ -H Authorization: hmac-auth-v1# ACCESS_KEY # base64_encode(SIGNATURE) # ALGORITHM # DATE # SIGNED_HEADERS -iHTTP/1.1 200 OK Content-Type: text/html Content-Length: 13175 ... Accept-Ranges: bytes从源码 apisix/plugins/hmac-auth.lua 可以看到当请求头中没有X-HMAC-ACCESS-KEY时插件会读取Authorization头按#拆分当拆分结果恰好为 6 段且首段为hmac-auth-v1时依次取出 access_key、signature、algorithm、date、signed_headers。测试用例 t/plugin/hmac-auth.t 验证了通过Authorization头传递认证信息并通过校验的场景其实际构造的认证字符串为local auth_string hmac-auth-v1# .. access_key .. # .. ngx_encode_base64(signature) .. # .. hmac-sha256# .. gmt .. #x-custom-header-a;x-custom-header-b方式三签名放在单独请求头中curl http://127.0.0.1:9080/index.html \ -H X-HMAC-SIGNATURE: base64_encode(SIGNATURE) \ -H X-HMAC-ALGORITHM: ALGORITHM \ -H Date: DATE \ -H X-HMAC-ACCESS-KEY: ACCESS_KEY \ -H X-HMAC-SIGNED-HEADERS: SIGNED_HEADERS -iHTTP/1.1 200 OK Content-Type: text/html Content-Length: 13175 ... Accept-Ranges: bytes注意多个签名请求头之间必须以;分隔例如x-custom-header-a;x-custom-header-bSIGNATURE需要先做 base64 编码再放入请求头源码在 apisix/plugins/hmac-auth.lua 用ngx.decode_base64(params.signature)解码后与计算值比对因此客户端侧应使用base64_encode。使用自定义请求头名默认使用的认证相关请求头名称为用途默认请求头签名X-HMAC-SIGNATURE算法X-HMAC-ALGORITHM日期Dateaccess keyX-HMAC-ACCESS-KEY签名请求头列表X-HMAC-SIGNED-HEADERS请求体摘要X-HMAC-DIGEST你可以在配置文件conf/config.yaml的plugin_attr中为这些头自定义名称plugin_attr结构可参考 conf/config.yaml.example 附近的注释plugin_attr: hmac-auth: signature_key: X-APISIX-HMAC-SIGNATURE algorithm_key: X-APISIX-HMAC-ALGORITHM date_key: X-APISIX-DATE access_key: X-APISIX-HMAC-ACCESS-KEY signed_headers_key: X-APISIX-HMAC-SIGNED-HEADERS body_digest_key: X-APISIX-HMAC-BODY-DIGEST配置后即可使用新请求头名发起请求curl http://127.0.0.1:9080/index.html \ -H X-APISIX-HMAC-SIGNATURE: base64_encode(SIGNATURE) \ -H X-APISIX-HMAC-ALGORITHM: ALGORITHM \ -H X-APISIX-DATE: DATE \ -H X-APISIX-HMAC-ACCESS-KEY: ACCESS_KEY \ -H X-APISIX-HMAC-SIGNED-HEADERS: SIGNED_HEADERS \ -H X-APISIX-HMAC-BODY-DIGEST: BODY_DIGEST -iHTTP/1.1 200 OK Content-Type: text/html Content-Length: 13175 ... Accept-Ranges: bytes源码 apisix/plugins/hmac-auth.lua 中的get_params函数会优先读取plugin.plugin_attr(plugin_name)配置的自定义头名未配置时才回退到默认值。删除插件删除插件时将 Route 配置中的plugins置空即可。APISIX 会自动热加载配置无需重启curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: $admin_key -X PUT -d { uri: /index.html, plugins: {}, upstream: { type: roundrobin, nodes: { 127.0.0.1:1980: 1 } } }同样地若不再需要某个 Consumer 的 hmac-auth 配置也可以从对应 Consumer 的plugins中移除hmac-auth配置块。常见排错与注意事项结合源码 apisix/plugins/hmac-auth.lua 与测试用例 t/plugin/hmac-auth.t以下排查点值得注意401 且 error log 提示access key or signature missing请求未携带X-HMAC-ACCESS-KEY、X-HMAC-SIGNATURE或有效的Authorization头t/plugin/hmac-auth.talgorithm missing或algorithm x not supported请求头中的X-HMAC-ALGORITHM缺失或与 Consumer 配置的 algorithm 不一致t/plugin/hmac-auth.tClock skew exceededDate头与网关时间差超过clock_skew或clock_skew为 0 时携带了无法解析的日期注意clock_skew设为 0 表示跳过日期校验若设为正值则必须提供合法 GMT 格式的Date头t/plugin/hmac-auth.tInvalid signature签名字符串拼接顺序、大小写、URI 编码方式或请求头值不一致。请重点核对待签字符串是否以换行符结尾、name:value中冒号后是否有空格、canonical_query_string是否按字典序排序Invalid signed header xX-HMAC-SIGNED-HEADERS中声明了 Consumer 配置signed_headers之外的请求头t/plugin/hmac-auth.tInvalid digest/Exceed body limit size启用validate_request_body后X-HMAC-DIGEST缺失或与请求体摘要不一致或请求体超过max_req_body限制keep_headers行为默认false情况下认证成功后插件会移除X-HMAC-SIGNATURE、X-HMAC-ALGORITHM、X-HMAC-SIGNED-HEADERS三个请求头避免上游服务收到无关认证头apisix/plugins/hmac-auth.lua需要上游读取这些头时再开启keep_headers。小结hmac-auth是 APISIX 认证插件体系中实现请求签名防篡改的成熟方案通过 Consumer 上的属性配置 完成密钥与算法管理在 Route/Service 上声明启用由网关在 rewrite 阶段完成签名、时间窗与请求体的三重校验。理解其signing_string拼接规则方法、URI、规范化查询串、access_key、GMT 时间、签名请求头以\n连接并结尾补\n是正确生成签名的关键官方文档 hmac-auth.md 中的逐步推导示例与 多语言签名生成示例配合 插件源码 与 测试用例可以为接入方提供从配置到联调的完整参考。【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考