Skip to content

Commit

Permalink
docs: update hmac-auth doc (apache#8192)
Browse files Browse the repository at this point in the history
  • Loading branch information
guitu168 authored and Liu-Junlin committed Nov 4, 2022
1 parent bb327e0 commit cd7f3ce
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 43 deletions.
10 changes: 8 additions & 2 deletions docs/en/latest/plugins/hmac-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,21 @@ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13

The formula for calculating the signature is `signature = HMAC-SHAx-HEX(secret_key, signing_string)`.

In order to generate the signature, two parameters, `secret_key` and `signing_string` are required. The `secret_key` is configured by a Consumer and the `signing_string` is calculated as `signing_string = HTTP Method + \n + HTTP URI + \n + canonical_query_string + \n + access_key + \n + Date + \n + signed_headers_string`. If any of the terms are missing, they are replaced by an empty string. The different terms in this calculation are explained below:
In order to generate the signature, two parameters, `secret_key` and `signing_string` are required. The `secret_key` is configured by a Consumer and the `signing_string` is calculated as `signing_string = HTTP Method + \n + HTTP URI + \n + canonical_query_string + \n + access_key + \n + Date + \n + signed_headers_string`. The different terms in this calculation are explained below:

- **HTTP Method** : HTTP request method in uppercase. For example, GET, PUT, POST etc.
- **HTTP URI** : HTTP URI. Should start with "/" and "/" denotes an empty path.
- **Date** : Date in the HTTP header in GMT format.
- **canonical_query_string** : The result of encoding the query string in the URL (the string "key1 = value1 & key2 = value2" after the "?" in the URL).
- **signed_headers_string** : Concatenation of the specified request headers.

The algorithm for generating `canonical_query_string` is described below:
:::tip

If any of the terms are missing, they are replaced by an empty string.

:::

**The algorithm for generating `canonical_query_string` is described below:**

1. Extract the query terms from the URL.
2. Split the query terms into key-value pairs by using `&` as the separator.
Expand Down
101 changes: 60 additions & 41 deletions docs/zh/latest/plugins/hmac-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ curl http://127.0.0.1:9180/apisix/admin/consumers \
}'
```

<!--
你也可以通过 [APISIX Dashboard](/docs/dashboard/USER_GUIDE) 的 Web 界面完成操作。
<!--
![create a consumer](https://raw.githubusercontent.com/apache/apisix/master/docs/assets/images/plugin/hmac-auth-1.png)
![enable hmac plugin](https://raw.githubusercontent.com/apache/apisix/master/docs/assets/images/plugin/hmac-auth-2.png)
Expand All @@ -94,21 +94,29 @@ curl http://127.0.0.1:9180/apisix/admin/routes/1 \
}'
```

## 测试插件
## 签名算法详解

### 签名生成公式

需注意,在使用 `hmac-auth` 插件时,会涉及到签名。签名的计算公式为 `signature = HMAC-SHAx-HEX(secret_key, signing_string)`
在使用 `hmac-auth` 插件时,会涉及到签名。签名的计算公式为 `signature = HMAC-SHAx-HEX(secret_key, signing_string)`

为了生成签名需要两个参数:`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`如果 `signing_string` 中的某一项不存在,则需要使用一个空字符串代替
为了生成签名需要两个参数:`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**:HTTP URI。必须以 “/” 开头,“/” 表示空路径。
- **HTTP URI**:HTTP URI。必须以 `/` 开头,`/` 表示空路径。
- **Date**:请求头中的日期(GMT 格式)。
- **canonical_query_string**:对 URL 中的 query(query 即 URL 中 `?` 后面的 `key1=valve1&key2=valve2` 字符串)进行编码后的结果。
- **signed_headers_string**:从请求头中获取客户端指定的字段,并按顺序拼接字符串的结果。

> 生成 `canonical_query_string` 的算法描述如下:
:::tip 提示

- 如果 `signing_string` 中的任意一项不存在,则需要使用一个空字符串代替。

- 由于签名计算时,会区分大小写字母,在使用时,请规范其参数命名。

:::

**生成 `canonical_query_string` 的算法描述如下:**

1. 提取 URL 中的 query 项。
2. 使用 `&` 作为分隔符,将 query 拆分成键值对。
Expand All @@ -124,7 +132,7 @@ curl http://127.0.0.1:9180/apisix/admin/routes/1 \
- 当该项同时有 `key``value` 时,转换公式为 `key + "=" + value` 。此处 `value` 可以是空字符串。
- 将每一项转换后,以 `key` 按照字典顺序(ASCII 码由小到大)排序,并使用 `&` 符号连接起来,生成相应的 `canonical_query_string`

> 生成 `signed_headers_string` 的算法如下:
**生成 `signed_headers_string` 的算法如下:**

1. 从请求头中获取指定的 headers 加入计算中。
2. 从请求头中按顺序取出 `SIGNED_HEADERS` 指定的 headers,并按顺序用 `name:value` 方式拼接起来,拼接完后就生成了 `signed_headers_string`
Expand All @@ -136,7 +144,9 @@ HeaderKey2 + ":" + HeaderValue2 + "\n"\+
HeaderKeyN + ":" + HeaderValueN + "\n"
```

以下示例为你展示了签名字符串的拼接:
### 签名生成公式过程详解

接下来,我们将以下述请求为例,为你介绍签名生成公式的具体计算过程:

```shell
curl -i http://127.0.0.1:9080/index.html?name=james&age=36 \
Expand All @@ -145,30 +155,28 @@ curl -i http://127.0.0.1:9080/index.html?name=james&age=36 \
-H "User-Agent: curl/7.29.0"
```

### 签名生成公式过程详解

1. 上文请求默认的 HTTP Method 是 GET,得到 `signing_string`
1. 上文请求默认的 HTTP Method 是 GET,得到 `signing_string` 为:

```plain
"GET"
```

2. 请求的 URI 是 `/index.html`,根据 HTTP Method + \n + HTTP URI 得到 `signing_string`
2. 请求的 URI 是 `/index.html`,根据 HTTP Method + \n + HTTP URI 得到 `signing_string`

```plain
"GET
/index.html"
```

3. URL 中的 query 项是 `name=james&age=36`,假设 `encode_uri_params` 为 false,根据 `canonical_query_string` 的算法,重点是对 `key` 进行字典排序,得到 `age=36&name=james`;根据 HTTP Method + \n + HTTP URI + \n + canonical_query_string 得到 `signing_string`
3. URL 中的 query 项是 `name=james&age=36`,假设 `encode_uri_params` 为 false,根据 `canonical_query_string` 的算法,重点是对 `key` 进行字典排序,得到 `age=36&name=james`;根据 HTTP Method + \n + HTTP URI + \n + canonical_query_string 得到 `signing_string`

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

4. access_key 是 `user-key`,根据 HTTP Method + \n + HTTP URI + \n + canonical_query_string + \n + access_key 得到 `signing_string`
4. access_key 是 `user-key`,根据 HTTP Method + \n + HTTP URI + \n + canonical_query_string + \n + access_key 得到 `signing_string`

```plain
"GET
Expand All @@ -177,7 +185,7 @@ age=36&name=james
user-key"
```

5. Date 是指 GMT 格式的日期,形如 `Tue, 19 Jan 2021 11:33:20 GMT`, 根据 HTTP Method + \n + HTTP URI + \n + canonical_query_string + \n + access_key + \n + Date 得到 `signing_string`
5. Date 是指 GMT 格式的日期,形如 `Tue, 19 Jan 2021 11:33:20 GMT`, 根据 HTTP Method + \n + HTTP URI + \n + canonical_query_string + \n + access_key + \n + Date 得到 `signing_string`

```plain
"GET
Expand All @@ -189,7 +197,7 @@ Tue, 19 Jan 2021 11:33:20 GMT"

6. `signed_headers_string` 用来制定参与到签名的 headers,在上面示例中包括 `User-Agent: curl/7.29.0``x-custom-a: test`

根据 HTTP Method + \n + HTTP URI + \n + canonical_query_string + \n + access_key + \n + Date + \n + signed_headers_string + `\n`,得到完整的 `signing_string`
根据 HTTP Method + \n + HTTP URI + \n + canonical_query_string + \n + access_key + \n + Date + \n + signed_headers_string + `\n`,得到完整的 `signing_string`

```plain
"GET
Expand All @@ -202,7 +210,36 @@ x-custom-a:test
"
```

以下示例是通过使用 Python 来生成签名 `SIGNATURE`
### Body 校验

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

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

如果没有请求 body,你可以将 `X-HMAC-DIGEST` 的值设置为空字符串的 HMAC-SHA。

:::note 注意

当开启 body 校验时,为了计算请求 body 的 `hmac-sha` 值,该插件会把 body 加载到内存中,在请求 body 较大的情况下,可能会造成较高的内存消耗。

为了避免这种情况,你可以通过设置 `max_req_body`(默认值是 512KB)配置项来配置最大允许的 body 大小,body 超过此大小的请求会被拒绝。

:::

## 测试插件

假设当前请求为:

```shell
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"
```

通过以下 Python 代码为上述请求生成签名 `SIGNATURE`

```python
import base64
Expand All @@ -229,27 +266,9 @@ print(base64.b64encode(hash.digest()))
| --------- | -------------------------------------------- |
| SIGNATURE | 8XV1GB7Tq23OJcoz6wjqTs4ZLxr9DiLoY4PxzScWGYg= |

您也可以参考 [Generating HMAC signatures](../examples/plugins-hmac-auth-generate-signature.md) 了解如何为不同的编程语言生成签名。

### Body 校验

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

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

如果没有请求 body,你可以将 `X-HMAC-DIGEST` 的值设置为空字符串的 HMAC-SHA。

:::note

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

:::

### 使用生成好的签名进行请求尝试
你也可以参考 [Generating HMAC signatures](../examples/plugins-hmac-auth-generate-signature.md) 了解如何为不同的编程语言生成签名。

你可以通过以下示例使用生成的签名发起请求:
签名生成后,你可以通过以下示例使用生成的签名发起请求:

```shell
curl -i "http://127.0.0.1:9080/index.html?name=james&age=36" \
Expand All @@ -272,7 +291,7 @@ Server: APISIX/2.2
......
```

签名可以放到请求头 `Authorization` 字段中:
你也可以将签名放到请求头 `Authorization` 字段中:

```shell
curl http://127.0.0.1:9080/index.html \
Expand All @@ -291,7 +310,7 @@ Accept-Ranges: bytes
...
```

也可以将签名单独放在另一个请求头中
还可以将签名单独放在另一个请求头中

```shell
curl http://127.0.0.1:9080/index.html \
Expand All @@ -313,15 +332,15 @@ Accept-Ranges: bytes
<html lang="cn">
```

:::note
:::note 注意

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 名称
### 自定义 header 名称

除了配置签名外,你还可以在配置文件(`conf/config.yaml`)中的`plugin_attr` 配置项下,添加 `hmac-auth` 插件的属性来自定义参数 header 名称。如下所示:

Expand Down

0 comments on commit cd7f3ce

Please sign in to comment.