Files
2024-09-12 21:48:40 +08:00

12 KiB
Raw Permalink Blame History

title, keywords, description
title keywords description
HMAC 认证
higress
hmac auth
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字段不能重复:

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两个路由做如下插件配置

allow:
- consumer1

在*.example.com和test.com两个域名做如下插件配置

allow:
- consumer2

若是在控制台进行配置此例指定的route-a和route-b即在创建网关路由时填写的路由名称当匹配到这两个路由时将允许name为consumer1的调用者访问其他调用者不允许访问。

此例指定的*.example.com和test.com用于匹配请求的域名当发现域名匹配时将允许name为consumer2的调用者访问其他调用者不被允许访问。

网关实例级别开启

以下配置将对网关实例级别开启 Hamc Auth 认证,所有请求均需要经过认证后才能访问。

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请求

如下图所示:

签名串提取流程

客户端需要从Http请求中提取出关键数据组合成一个签名串生成的签名串的格式如下

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值的参考计算方式

String content-MD5 = Base64.encodeBase64(MD5(bodyStream.getbytes("UTF-8")));
  • Content-Type请求中的Content-Type头的值可为空

  • Date请求中的Date头的值当未开启date_offset配置时,可为空,否则将用于时间偏移校验

  • Headers用户可以选取指定的header参与签名关于header的签名串拼接方式有以下规则

    • 参与签名计算的Header的Key按照字典排序后使用如下方式拼接
    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中的所有参数具体组织形式如下

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请求

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

生成的正确签名串为:

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 是最终生成的签名:

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请求的示例

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. 从接收到的请求中读取客户端签名,对比服务器端签名和客户端签名的一致性

如下图所示:

签名排错方法

网关签名校验失败时会将服务端的签名串StringToSign放到HTTP Response的Header中返回到客户端Key为X-Ca-Error-Message用户只需要将本地计算的签名串StringToSign与服务端返回的签名串进行对比即可找到问题

如果服务端与客户端的StringToSign一致请检查用于签名计算的APP Secret是否正确

因为HTTP Header中无法表示换行因此StringToSign中的换行符都被替换成#,如下所示:

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 请求的调用方无访问权限