为什么要做API签名
签名通过以下方式帮助保护请求:
验证请求者的身份
签名确保请求是由持有有效访问密钥的人发送的保护传输中的数据
为了防止请求在传输过程中被篡改,API提供方 会使用请求参数来计算请求的哈希值,并将生成的哈希值加密后作为请求的一部分,发送到 API提供方。服务器会使用收到的请求参数以同样的过程计算哈希值,并验证请求中的哈希值。如果请求被篡改,将导致哈希值不一致,API提供方 将拒绝本次请求。
如何进行API签名
申请安全凭证
本文使用的安全凭证为密钥,密钥包括 AccessKey
和 SecretKey
。每个用户最多可以拥有两对密钥。
AccessKey
:用于标识 API 调用者身份,可以简单类比为用户名。SecretKey
:用于验证 API 调用者的身份,可以简单类比为密码。- 用户必须严格保管安全凭证,避免泄露,否则将危及财产安全。如已泄漏,请立刻禁用该安全凭证。
以上安全凭证的颁发,是由API接口提供方进行颁发,具体如何设计网上大把文章,可自行搜索参考
签名过程
假设API提供商颁发给用户的凭证如下:
AccessKey
: a416230515674b29a7ca05b0e3f135c4SecretKey
: 74b65fd87b354a8ea873332b53a0cf0e。
拼接待签名字符串
按如下伪代码格式拼接规范请求串:1
StringToSign = HTTPMethod + Timestamp + SignatureNonce
字段名称 解释 示例值 HTTPMethod HTTP 请求方法(GET、POST )。本示例取值为 POST
。POST Timestamp 时间戳。本示例采用 RFC3339
标准时间戳2022-07-11T19:14:41Z SignatureNonce 签名字符串,随机值,⽤于防⽌⽹络重放攻击。在不 同请求间要使⽤不同的随机数值。 52a82619176942e597a6c946963ffd0c 计算签名
我们此处使用
HMAC-SHA1
签名认证算法来实现本次签名示例,也可使用其他算法,主要是需要考虑跨语言是否有标准库实现了该算法本文我们以
go
语言来实现签名,具体的伪代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31// Sign API接口签名
func (s *blogApiService) Sign(ctx context.Context, req *http.Request) *http.Request {
// 定义签名所需query参数
q := req.URL.Query()
// 此处的 `AccessKeyId` 可以放在Apollo或者配置库中,可能多环境的 AK/SK并不一致
q.Add("AccessKeyId", "a416230515674b29a7ca05b0e3f135c4")
q.Add("Timestamp", time.Now().UTC().Format(time.RFC3339))
q.Add("SignatureNonce", uuid.New().String())
// 拼接签名字符串
StringToSign := []byte(req.Method + "&" + q.Get("Timestamp") + "&" + q.Get("SignatureNonce"))
// 获取签名结果,此处的 `SecretKey` 可以放在Apollo或者配置库中,可能多环境的 AK/SK并不一致
key := []byte("74b65fd87b354a8ea873332b53a0cf0e")
mac := hmac.New(sha1.New, key)
mac.Write(StringToSign)
cip := mac.Sum(nil)
// 将结果进行url编码,防止在 http 请求过程中进行了编码,导致API提供方无法识别
signature := fmt.Sprintf("%v", base64.URLEncoding.EncodeToString(cip))
// 设置签名结果
q.Add("Signature", signature)
req.URL.RawQuery = q.Encode()
// 定义通用请求头
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
return req
}上述方法中,外部构建好
http.Request
后,只需要将http.Request
传入到该方法中,即可完成签名字符串的获取。最终在签名完成后,得到的 http Query应该是这样的:
1
AccessKeyId=a416230515674b29a7ca05b0e3f135c4&Signature=GgLvBm1EaaOI4kZ9i6s4BBoY0W4%3D&Timestamp=2022-07-11T19%3A14%3A41Z&SignatureNonce=fanerblog
API提供方验证签名
道理其实一致,只需要按照相同方法进行签名,最后比对签名是否一致,若一致则表示该请求确实来自颁发凭证的调用方发起。
因为SecretKey
是仅有颁发者和被颁发者拥有的,若SecretKey
不一致,产生的签名也无法校验通过。此处就不进行过多的演示了
三、结语
以上仅是对API签名的简单演示与原理讲解,目前主流的签名算法在网上比较多,大家可自行参考,以确保自己提供给到第三方的接口安全性问题,若是计费型接口,被他人盗用,造成的损失是无法估量的。