为什么要做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签名的简单演示与原理讲解,目前主流的签名算法在网上比较多,大家可自行参考,以确保自己提供给到第三方的接口安全性问题,若是计费型接口,被他人盗用,造成的损失是无法估量的。