hmac-auth

    名字

    添加 HMAC Authentication 到一个 serviceroute。 然后 consumer 将其签名添加到请求头以验证其请求。

    如何启用

    1. 创建一个 consumer 对象,并设置插件 hmac-auth 的值。

    默认 keep_headers 为 false,encode_uri_param 为 true。

    1. 创建 Route 或 Service 对象,并开启 hmac-auth 插件。
    1. curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{ "uri": "/index.html", "plugins": { "hmac-auth": {} }, "upstream": { "type": "roundrobin", "nodes": { "39.97.63.215:80": 1 } }}'

    签名的计算公式为 signature = HMAC-SHAx-HEX(secret_key, signing_string),从公式可以看出,想要获得签名需要得到 和 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。如果 signing_string 中的某一项不存在,也需要使用一个空字符串代替。

    1. HTTP Method:指 HTTP 协议中定义的 GET、PUT、POST 等请求方法,必须使用全大写的形式。
    2. HTTP URI:要求必须以“/”开头,不以“/”开头的需要补充上,空路径为“/”。
    3. Date:请求头中的 Date ( GMT 格式 )。
    4. canonical_query_string:是对于 URL 中的 query( query 即 URL 中 ? 后面的 key1=valve1&key2=valve2 字符串)进行编码后的结果。
    5. signed_headers_string:是从请求头中获取客户端指定的字段,并按顺序拼接字符串的结果。
    • 提取 URL 中的 query 项,即 URL 中 ? 后面的 key1=valve1&key2=valve2 字符串。
    • 将 query 根据&分隔符拆开成若干项,每一项是 key=value 或者只有 key 的形式。
    • 根据 uri 参数是否编码,有下面两种情况:
    • encode_uri_param 为 true 时:
      • 对拆开后的每一项进行编码处理,分以下两种情况:
      • 当该项只有 key 时,转换公式为 url_encode(key) + “=” 的形式。
      • 当该项是 key=value 的形式时,转换公式为 url_encode(key) + “=” + url_encode(value) 的形式。这里 value 可以是空字符串。
      • 将每一项转换后,以 key 按照字典顺序( ASCII 码由小到大)排序,并使用 & 符号连接起来,生成相应的 canonical_query_string 。
    • encode_uri_param 为 false 时:
      • 对拆开后的每一项进行编码处理,分以下两种情况:
      • 当该项只有 key 时,转换公式为 key + “=” 的形式。
      • 当该项是 key=value 的形式时,转换公式为 key + “=” + value 的形式。这里 value 可以是空字符串。
      • 将每一项转换后,以 key 按照字典顺序( ASCII 码由小到大)排序,并使用 & 符号连接起来,生成相应的 canonical_query_string 。

    signed_headers_string 生成步骤如下:

    • 从请求头中按顺序取出 SIGNED_HEADERS 指定的 headers ,并按顺序用name:value方式拼接起来,拼接完后就生成了 signed_headers_string
    1. HeaderKey1 + ":" + HeaderValue1 + "\n"\+HeaderKey2 + ":" + HeaderValue2 + "\n"\+...HeaderKeyN + ":" + HeaderValueN + "\n"

    签名字符串拼接示例

    以下面请求为例:

    1. $ 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"

    注意:最后一个请求头也需要 + \n

    生成签名

    使用 Python 来生成签名 SIGNATURE

    1. import base64import hashlibimport hmac
    2. secret = bytes('my-secret-key', 'utf-8')message = bytes("""GET/index.htmlage=36&name=jamesuser-keyTue, 19 Jan 2021 11:33:20 GMTUser-Agent:curl/7.29.0x-custom-a:test""", 'utf-8')
    3. hash = hmac.new(secret, message, hashlib.sha256)
    4. # to lowercase base64print(base64.b64encode(hash.digest()))

    设置为 true 时,插件将计算请求 body 的 hmac-sha 值,并与请求 headers 中的 X-HMAC-DIGEST 的值进行校验。

    1. X-HMAC-DIGEST: base64(hmac-sha(<body>))

    当没有请求 body 时,插件会计算长度为 0 的空字符串的 hmac-sha 值。

    注:当开启 body 校验时,为了计算请求 body 的 hmac-sha 值,插件会把 body 加载到内存中,在请求 body 较大的情况下,可能会造成较高的内存消耗。插件提供了 max_req_body(默认值 512KB) 配置项来配置最大允许的 body 大小,body 超过此大小的请求会被拒绝。

    1. $ 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"
    2. HTTP/1.1 200 OKContent-Type: text/html; charset=utf-8Transfer-Encoding: chunkedConnection: keep-aliveDate: Tue, 19 Jan 2021 11:33:20 GMTServer: APISIX/2.2......

    下面是签名信息的两种组装形式

    • 签名信息拼一起放到请求头 Authorization 字段中:
    • 签名信息分开分别放到请求头:
    1. $ 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 OKContent-Type: text/htmlContent-Length: 13175...Accept-Ranges: bytes
    2. <!DOCTYPE html><html lang="cn">
    1. ACCESS_KEY, SIGNATURE, ALGORITHM, DATE, SIGNED_HEADERS 分别代表对应的变量
    2. SIGNED_HEADERS 为客户端指定的加入加密计算的 headers。若存在多个 headers 需以 “;” 分割:x-custom-header-a;x-custom-header-b
    3. SIGNATURE 需要使用 base64 进行加密:base64_encode(SIGNATURE)

    自定义 header 名称

    我们可以在 conf/config.yaml 中,plugin_attr 下添加插件的属性配置来自定义参数 header 名称。

    1. 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

    自定义 header 后,请求示例:

    1. $ 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 OKContent-Type: text/htmlContent-Length: 13175...Accept-Ranges: bytes
    2. <!DOCTYPE html><html lang="cn">

    当你想去掉 hmac-auth 插件的时候,很简单,在插件的配置中把对应的 json 配置删除即可,无须重启服务,即刻生效:

    1. $ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{ "uri": "/index.html", "plugins": {}, "upstream": { "type": "roundrobin", "nodes": { "39.97.63.215:80": 1 } }}'

    签名生成示例

    以 HMAC SHA256 为例,介绍一下各种语言的签名生成示例。需要注意各种语言中对签名字符串的换行符的处理方式,这很容易导致出现 {"message":"Invalid signature"} 的问题。

    示例入参说明:

    示例出参说明:

    具体代码请参考: