news 2026/9/15 12:44:15

Apache APISIX hmac-auth 插件实战:HMAC 签名认证的配置、签名计算与源码解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Apache APISIX hmac-auth 插件实战:HMAC 签名认证的配置、签名计算与源码解析

Apache APISIX hmac-auth 插件实战:HMAC 签名认证的配置、签名计算与源码解析

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

hmac-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-auth,HMAC 认证将请求方法、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 can't 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_skewinteger0-签名允许的时钟偏移(秒)。设为0时跳过日期校验
signed_headersarray[string]--参与签名计算的请求头列表。指定后客户端请求只能携带这些指定请求头;未指定时全部请求头参与计算
keep_headersbooleanfalse[true, false]true时,认证成功后保留请求头X-HMAC-SIGNATUREX-HMAC-ALGORITHMX-HMAC-SIGNED-HEADERS,否则移除
encode_uri_paramsbooleantrue[true, false]true时对 URI 参数做 URL 编码,例如params1=hello%2Cworld会被编码,而params2=hello,world不会被编码
validate_request_bodybooleanfalse[true, false]true时校验请求体
max_req_bodyinteger512 * 1024-允许的最大请求体大小(字节)

对应地,apisix/plugins/hmac-auth.lua 中的consumer_schema给出了更精确的约束细节,可作为配置校验的参考:

  • access_keysecret_key:字符串,长度限制 1~256;
  • signed_headers中的每个请求头名称:字符串,长度限制 1~50;
  • max_req_body默认值为MAX_REQ_BODY,即1024 * 512(512KB),定义于 apisix/plugins/hmac-auth.lua;
  • required = {"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的 Consumer:

curl 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_keyconf/config.yaml中提取(该文件位于仓库 conf/config.yaml,默认配置见 conf/config.yaml.example):

admin_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_keysecret_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、POST;
  • HTTP URI:请求 URI,必须以/开头,/表示空路径;
  • Date:HTTP 头中的日期,GMT 格式;
  • canonical_query_string:URL 中查询字符串(问号?之后的key1=value1&key2=value2)规范化编码后的结果;
  • signed_headers_string:参与签名计算的指定请求头拼接结果。

缺失项处理:上述任何一项缺失时,都以空字符串参与计算(见 apisix/plugins/hmac-auth.lua 中core.table.concat(signing_string_items, "\n") .. "\n"的实现,末尾还会追加一个换行符)。

canonical_query_string 生成算法

  1. 从 URL 中提取查询项;
  2. &为分隔符将查询项拆分为键值对;
  3. encode_uri_paramstrue
    • 只有 key 时,转换公式为uri_encode(key) + "="
    • 同时存在 key 和 value 时,转换公式为uri_encode(key) + "=" + uri_encode(value)(value 可以为空字符串);
    • 按 key 的字典序排序,用&连接生成canonical_query_string
  4. encode_uri_paramsfalse
    • 只有 key 时,转换公式为key + "="
    • 同时存在 key 和 value 时,转换公式为key + "=" + value(value 可以为空字符串);
    • 按 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 生成算法

  1. 从请求头中取出需要参与计算的指定请求头;
  2. name:value格式拼接,得到signed_headers_string
HeaderKey1 + ":" + HeaderValue1 + "\n" HeaderKey2 + ":" + HeaderValue2 + "\n" ... HeaderKeyN + ":" + HeaderValueN + "\n"

注意:namevalue之间使用冒号直接连接,没有空格。源码中的实现为h .. ":" .. canonical_header(apisix/plugins/hmac-auth.lua),其中canonical_header通过core.request.header(ctx, h)读取,缺失时取空字符串。

签名生成全流程逐步推导

以下请求为例:

curl -i http://127.0.0.1:9080/index.html?name=james&age=36 \ -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

请求默认方法为 GET,signing_string为:

"GET"

第 2 步:拼接 HTTP URI

请求 URI 为/index.html,得到:

"GET /index.html"

第 3 步:拼接 canonical_query_string

URL 中查询项为name=james&age=36,假设encode_uri_paramsfalse。按 canonical_query_string 算法对 key 做字典排序得到age=36&name=james

"GET /index.html age=36&name=james"

第 4 步:拼接 access_key

access_keyuser-key

"GET /index.html age=36&name=james user-key"

第 5 步:拼接 Date

Date 为 GMT 格式,如Tue, 19 Jan 2021 11:33:20 GMT

"GET /index.html age=36&name=james user-key Tue, 19 Jan 2021 11:33:20 GMT"

第 6 步:拼接 signed_headers_string

signed_headers指定参与签名的请求头,示例中为User-Agent: curl/7.29.0x-custom-a: test

"GET /index.html age=36&name=james 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 age=36&name=james 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函数按以下顺序完成校验:

  1. 检查access_keysignaturealgorithm是否存在;
  2. 通过get_consumer(access_key)查找 Consumer(apisix/plugins/hmac-auth.lua),若 key 无效返回"Invalid access key"
  3. 校验请求携带的algorithm与 Consumer 配置的algorithm是否一致,否则返回"algorithm <x> not supported"
  4. clock_skew > 0,用ngx.parse_http_time解析 Date 头并计算与网关当前时间的差值,超过clock_skew则返回"Clock skew exceeded"
  5. 若配置了signed_headers,校验请求声明的签名请求头是否都在允许列表中,否则返回"Invalid signed header <x>"
  6. 用 Consumer 的secret_key与请求参数重新计算签名,与请求携带的 base64 解码后的签名比较,不一致返回"Invalid signature"

测试用例 t/plugin/hmac-auth.t 逐一验证了上述失败分支:缺失签名、缺失算法、无效 access key、不支持算法、时钟偏移超限、非法 GMT 时间等场景均返回 401 与{"message":"client request can't 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?name=james&age=36" \ -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; charset=utf-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' -i
HTTP/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' -i
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 13175 ... Accept-Ranges: bytes

注意

  1. 多个签名请求头之间必须以;分隔,例如x-custom-header-a;x-custom-header-b
  2. SIGNATURE需要先做 base64 编码再放入请求头(源码在 apisix/plugins/hmac-auth.lua 用ngx.decode_base64(params.signature)解码后与计算值比对,因此客户端侧应使用base64_encode)。

使用自定义请求头名

默认使用的认证相关请求头名称为:

用途默认请求头
签名X-HMAC-SIGNATURE
算法X-HMAC-ALGORITHM
日期Date
access keyX-HMAC-ACCESS-KEY
签名请求头列表X-HMAC-SIGNED-HEADERS
请求体摘要X-HMAC-DIGEST

你可以在配置文件conf/config.yamlplugin_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' -i
HTTP/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-KEYX-HMAC-SIGNATURE或有效的Authorization头(t/plugin/hmac-auth.t);
  • algorithm missingalgorithm <x> not supported:请求头中的X-HMAC-ALGORITHM缺失,或与 Consumer 配置的 algorithm 不一致(t/plugin/hmac-auth.t);
  • Clock skew exceededDate头与网关时间差超过clock_skew,或clock_skew为 0 时携带了无法解析的日期;注意clock_skew设为 0 表示跳过日期校验,若设为正值则必须提供合法 GMT 格式的Date头(t/plugin/hmac-auth.t);
  • Invalid signature:签名字符串拼接顺序、大小写、URI 编码方式或请求头值不一致。请重点核对:待签字符串是否以换行符结尾、name:value中冒号后是否有空格、canonical_query_string是否按字典序排序;
  • Invalid signed header <x>X-HMAC-SIGNED-HEADERS中声明了 Consumer 配置signed_headers之外的请求头(t/plugin/hmac-auth.t);
  • Invalid digest/Exceed body limit size:启用validate_request_body后,X-HMAC-DIGEST缺失或与请求体摘要不一致,或请求体超过max_req_body限制;
  • keep_headers行为:默认(false)情况下,认证成功后插件会移除X-HMAC-SIGNATUREX-HMAC-ALGORITHMX-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),仅供参考

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

不写后端,三步跑起一个类抖音短视频应用

不写后端&#xff0c;三步跑起一个类抖音短视频应用 【免费下载链接】douyin Vue3 Pinia 仿抖音&#xff0c;Vue 在移动端的最佳实践 . Imitate TikTok &#xff0c;Vue Best practices on Mobile 项目地址: https://gitcode.com/GitHub_Trending/do/douyin Douyin-Vu…

作者头像 李华
网站建设 2026/9/15 12:42:19

用 Twilio/SendGrid 为 IoT 地理围栏触发函数添加短信与邮件通知

用 Twilio/SendGrid 为 IoT 地理围栏触发函数添加短信与邮件通知 【免费下载链接】IoT-For-Beginners 12 Weeks, 24 Lessons, IoT for All! 项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners 本文围绕 IoT-For-Beginners 运输项目第 4 课&#xff08…

作者头像 李华
网站建设 2026/9/15 12:38:47

30分钟把FiftyOne部署到边缘设备:YOLOv8s计算机视觉应用完整实战

30分钟把FiftyOne部署到边缘设备&#xff1a;YOLOv8s计算机视觉应用完整实战 【免费下载链接】fiftyone Refine high-quality datasets and visual AI models 项目地址: https://gitcode.com/GitHub_Trending/fi/fiftyone FiftyOne 是一个用来构建高质量数据集和视觉 AI…

作者头像 李华