Files
higress/plugins/wasm-cpp/extensions/hmac_auth/README.md
2024-09-12 21:48:40 +08:00

299 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: HMAC 认证
keywords: [higress,hmac auth]
description: HMAC 认证插件配置参考
---
## 功能说明
`hmac-auth`插件实现了基于 HMAC 算法为 HTTP 请求生成不可伪造的签名,并基于签名实现身份认证和鉴权
## 运行属性
插件执行阶段:`认证阶段`
插件执行优先级:`330`
## 配置字段
**注意:**
- 在一个规则里,鉴权配置和认证配置不可同时存在
- 对于通过认证鉴权的请求请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
### 认证配置
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ------------- | --------------- | -------- | ------ | ------------------------------------------------------------------------------------------------------------------- |
| `global_auth` | bool | 选填(**仅实例级别配置** | - | 只能在实例级别配置若配置为true则全局生效认证机制; 若配置为false则只对做了配置的域名和路由生效认证机制若不配置则仅当没有域名和路由配置时全局生效兼容老用户使用习惯。 |
| `consumers` | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
| `date_offset` | number | 选填 | - | 配置允许的客户端最大时间偏移,单位为秒,根据请求头`Date`解析客户端 UTC 时间,可用于避免请求重放;未配置时,不做校验 |
`consumers`中每一项的配置字段说明如下:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| -------- | -------- | -------- | ------ | ----------------------------------- |
| `key` | string | 必填 | - | 配置从请求的`x-ca-key`头中提取的key |
| `secret` | string | 必填 | - | 配置用于生成签名的secret |
| `name` | string | 必填 | - | 配置该consumer的名称 |
### 鉴权配置(非必需)
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ----------- | --------------- | ------------------------------------------- | ------ | ----------------------------------------------------------- |
| `allow` | array of string | 选填(**非实例级别配置**) | - | 只能在路由或域名等细粒度规则上配置,对于符合匹配条件的请求,配置允许访问的 consumer从而实现细粒度的权限控制 |
## 配置示例
### 全局配置认证和路由粒度进行鉴权
在实例级别做如下插件配置, 注意`key`字段不能重复:
```yaml
global_auth: false
consumers:
- key: appKey-example-1
secret: appSecret-example-1
name: consumer-1
- key: appKey-example-2
secret: appSecret-example-2
name: consumer-2
```
route-a和route-b两个路由做如下插件配置
```yaml
allow:
- consumer1
```
在*.example.com和test.com两个域名做如下插件配置
```yaml
allow:
- consumer2
```
若是在控制台进行配置此例指定的route-a和route-b即在创建网关路由时填写的路由名称当匹配到这两个路由时将允许name为consumer1的调用者访问其他调用者不允许访问。
此例指定的*.example.com和test.com用于匹配请求的域名当发现域名匹配时将允许name为consumer2的调用者访问其他调用者不被允许访问。
### 网关实例级别开启
以下配置将对网关实例级别开启 Hamc Auth 认证,所有请求均需要经过认证后才能访问。
```yaml
global_auth: true
consumers:
- key: appKey-example-1
secret: appSecret-example-1
name: consumer-1
- key: appKey-example-2
secret: appSecret-example-2
name: consumer-2
```
## 签名机制说明
### 配置准备
如上指引,在插件配置中配置生成和验证签名需要用的凭证配置
- key: 用于请求头 `x-ca-key` 中设置
- secret: 用于生成请求签名
### 客户端签名生成方式
#### 流程简介
客户端生成签名一共分三步处理:
1. 从原始请求中提取关键数据,得到一个用来签名的字符串
2. 使用加密算法和配置的 `secret` 对关键数据签名串进行加密处理,得到签名
3. 将签名所相关的所有头加入到原始HTTP请求中得到最终HTTP请求
如下图所示:
![](https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/1745707061/p188113.png)
#### 签名串提取流程
客户端需要从Http请求中提取出关键数据组合成一个签名串生成的签名串的格式如下
```text
HTTPMethod
Accept
Content-MD5
Content-Type
Date
Headers
PathAndParameters
```
以上7个字段构成整个签名串字段之间使用\n间隔如果Headers为空则不需要加\n其他字段如果为空都需要保留\n。签名大小写敏感。下面介绍下每个字段的提取规则
- HTTPMethodHTTP的方法全部大写比如POST
- Accept请求中的Accept头的值可为空。建议显式设置 Accept Header。当 Accept 为空时,部分 Http 客户端会给 Accept 设置默认值为 `*/*`,导致签名校验失败。
- Content-MD5请求中的Content-MD5头的值可为空只有在请求存在Body且Body为非Form形式时才计算Content-MD5头下面是Java的Content-MD5值的参考计算方式
```java
String content-MD5 = Base64.encodeBase64(MD5(bodyStream.getbytes("UTF-8")));
```
- Content-Type请求中的Content-Type头的值可为空
- Date请求中的Date头的值当未开启`date_offset`配置时,可为空,否则将用于时间偏移校验
- Headers用户可以选取指定的header参与签名关于header的签名串拼接方式有以下规则
- 参与签名计算的Header的Key按照字典排序后使用如下方式拼接
```text
HeaderKey1 + ":" + HeaderValue1 + "\n"\+
HeaderKey2 + ":" + HeaderValue2 + "\n"\+
...
HeaderKeyN + ":" + HeaderValueN + "\n"
```
- 某个Header的Value为空则使用HeaderKey+":"+"\n"参与签名需要保留Key和英文冒号
- 所有参与签名的Header的Key的集合使用英文逗号分割放到Key为X-Ca-Signature-Headers的Header中
- 以下Header不参与Header签名计算X-Ca-Signature、X-Ca-Signature-Headers、Accept、Content-MD5、Content-Type、Date
- PathAndParameters: 这个字段包含PathQuery和Form中的所有参数具体组织形式如下
```text
Path + "?" + Key1 + "=" + Value1 + "&" + Key2 + "=" + Value2 + ... "&" + KeyN + "=" + ValueN
```
注意:
1. Query和Form参数对的Key按照字典排序后使用上面的方式拼接
2. Query和Form参数为空时则直接使用Path不需要添加?
3. 参数的Value为空时只保留Key参与签名等号不需要再加入签名
4. Query和Form存在数组参数时key相同value不同的参数 取第一个Value参与签名计算
#### 签名串提取示例
初始的HTTP请求
```text
POST /http2test/test?param1=test HTTP/1.1
host:api.aliyun.com
accept:application/json; charset=utf-8
ca_version:1
content-type:application/x-www-form-urlencoded; charset=utf-8
x-ca-timestamp:1525872629832
date:Wed, 09 May 2018 13:30:29 GMT+00:00
user-agent:ALIYUN-ANDROID-DEMO
x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
content-length:33
username=xiaoming&password=123456789
```
生成的正确签名串为:
```text
POST
application/json; charset=utf-8
application/x-www-form-urlencoded; charset=utf-8
Wed, 09 May 2018 13:30:29 GMT+00:00
x-ca-key:203753385
x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
x-ca-signature-method:HmacSHA256
x-ca-timestamp:1525872629832
/http2test/test?param1=test&password=123456789&username=xiaoming
```
#### 签名计算流程
客户端从HTTP请求中提取出关键数据组装成签名串后需要对签名串进行加密及编码处理形成最终的签名
具体的加密形式如下,其中 `stringToSign` 是提取出来的签名串,`secret` 就是插件配置中填写的,`sign` 是最终生成的签名:
```java
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
byte[] secretBytes = secret.getBytes("UTF-8");
hmacSha256.init(new SecretKeySpec(secretBytes, 0, secretBytes.length, "HmacSHA256"));
byte[] result = hmacSha256.doFinal(stringToSign.getBytes("UTF-8"));
String sign = Base64.encodeBase64String(result);
```
总结一下,就是将 `stringToSign` 使用UTF-8解码后得到Byte数组然后使用加密算法对Byte数组进行加密然后使用Base64算法进行编码形成最终的签名。
#### 添加签名流程
客户端需要将以下四个Header放在HTTP请求中传输给API网关进行签名校验
- x-ca-key取值APP Key必选
- x-ca-signature-method签名算法取值HmacSHA256或者HmacSHA1可选默认值为HmacSHA256
- x-ca-signature-headers所有签名头的Key的集合使用英文逗号分隔可选
- x-ca-signature签名必选
下面是携带签名的整个HTTP请求的示例
```text
POST /http2test/test?param1=test HTTP/1.1
host:api.aliyun.com
accept:application/json; charset=utf-8
ca_version:1
content-type:application/x-www-form-urlencoded; charset=utf-8
x-ca-timestamp:1525872629832
date:Wed, 09 May 2018 13:30:29 GMT+00:00
user-agent:ALIYUN-ANDROID-DEMO
x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
x-ca-key:203753385
x-ca-signature-method:HmacSHA256
x-ca-signature-headers:x-ca-timestamp,x-ca-key,x-ca-nonce,x-ca-signature-method
x-ca-signature:xfX+bZxY2yl7EB/qdoDy9v/uscw3Nnj1pgoU+Bm6xdM=
content-length:33
username=xiaoming&password=123456789
```
### 服务端签名验证方式
#### 流程简介
服务器验证客户端签名一共分四步处理:
1. 从接收到的请求中提取关键数据,得到一个用来签名的字符串
2. 从接收到的请求中读取 `key` ,通过 `key` 查询到对应的 `secret`
3. 使用加密算法和 `secret` 对关键数据签名串进行加密处理,得到签名
4. 从接收到的请求中读取客户端签名,对比服务器端签名和客户端签名的一致性
如下图所示:
![](https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/1745707061/p188116.png)
### 签名排错方法
网关签名校验失败时会将服务端的签名串StringToSign放到HTTP Response的Header中返回到客户端Key为X-Ca-Error-Message用户只需要将本地计算的签名串StringToSign与服务端返回的签名串进行对比即可找到问题
如果服务端与客户端的StringToSign一致请检查用于签名计算的APP Secret是否正确
因为HTTP Header中无法表示换行因此StringToSign中的换行符都被替换成`#`,如下所示:
```text
X-Ca-Error-Message: Server StringToSign:`GET#application/json##application/json##X-Ca-Key:200000#X-Ca-Timestamp:1589458000000#/app/v1/config/keys?keys=TEST`
```
## 相关错误码
| HTTP 状态码 | 出错信息 | 原因说明 |
| ----------- | ---------------------- | -------------------------------------------------------------------------------- |
| 401 | Invalid Key | 请求头未提供 x-ca-key或者 x-ca-key 无效 |
| 401 | Empty Signature | 请求头未提供 x-ca-signature 签名串 |
| 400 | Invalid Signature | 请求头 x-ca-signature 签名串,与服务端计算得到签名不一致 |
| 400 | Invalid Content-MD5 | 请求头 content-md5 不正确 |
| 400 | Invalid Date | 根据请求头 date 计算时间偏移超过配置的 date_offset |
| 413 | Request Body Too Large | 请求 Body 超过限制大小32 MB |
| 413 | Payload Too Large | 请求 Body 超过全局配置 DownstreamConnectionBufferLimits |
| 403 | Unauthorized Consumer | 请求的调用方无访问权限 |