API 接口请求签名实现

版本号 日期 修订要点 备注
V2.2.1 2022/1/7 新建

一、目的

对接口访问需要进行签名校验,本文提供签名算法说明和参考实现,方便开发人员快速接入。

二、签名算法

签名的生成要素主要由应用 appKey、应用 appSecret、随机数 rand、时间戳(单位秒)timestamp 按照固定的排列规则组成;生成的签名不能重复使用。

签名值生成过程:

  1. 获取 appKeyappSecret(由服务方提供,下文以占位符代替);
  2. 生成 4~6 位随机字符串;
  3. 生成当前时间戳(秒);
  4. 按固定规则拼接得到 plain 字符串:
    • 排列方式:appKey=%s&appSecret=%s&rand=%s&timestamp=%s
  5. 使用 HmacSHA256appSecret 为密钥对 plain 字符串做消息摘要;
  6. HTTP 请求头携带签名相关信息:x-appKey(应用 appKey)、x-signature(签名)、x-timestamp(时间戳)、x-rand(随机数)。

三、算法参考

Java

随机数生成:

 1public static final String BASE = "abcdefghijklmnopqrstuvwxyz0123456789";
 2
 3/**
 4 * 随机生成 4-6 位字符串
 5 */
 6public static String randStr() {
 7    StringBuilder sb = new StringBuilder();
 8    int length = (int) (Math.random() * 3 + 4);
 9    for (int i = 0; i < length; i++) {
10        int number = (int) (Math.random() * BASE.length());
11        sb.append(BASE.charAt(number));
12    }
13    return sb.toString();
14}

时间戳(秒):

1/**
2 * 获取当前时间戳
3 *
4 * @return 时间戳字符串
5 */
6public static String timestampStr() {
7    long second = Instant.now().getEpochSecond();
8    return String.valueOf(second);
9}

签名:

 1public static String getSign(String appKey, String appSecret, String rand, String timestamp) throws Exception {
 2    // 有随机数的签名
 3    String raw = "appKey=%s&appSecret=%s&rand=%s&timestamp=%s";
 4    String plain = String.format(raw, appKey, appSecret, rand, timestamp);
 5
 6    SecretKeySpec secretKeySpec = new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
 7    Mac mac = Mac.getInstance("HmacSHA256");
 8    mac.init(secretKeySpec);
 9    byte[] bytes = mac.doFinal(plain.getBytes());
10    return byte2HexString(bytes);
11}
12
13public static String byte2HexString(byte[] bytes) {
14    StringBuilder stringBuffer = new StringBuilder();
15    for (int i = 0; i < bytes.length; ++i) {
16        String temp = Integer.toHexString(bytes[i] & 255);
17        if (temp.length() == 1) {
18            stringBuffer.append("0");
19        }
20        stringBuffer.append(temp);
21    }
22    return stringBuffer.toString();
23}

Golang

随机数:

 1import (
 2	"bytes"
 3	"crypto/rand"
 4	"math/big"
 5	mrand "math/rand"
 6)
 7
 8// BASE 随机字符串
 9const BASE = "abcdefghijklmnopqrstuvwxyz0123456789"
10
11func RandStr() string {
12	var randStr string
13	b := bytes.NewBufferString(BASE)
14	length := 4 + mrand.Intn(3)
15	bigInt := big.NewInt(int64(b.Len()))
16	for i := 0; i < length; i++ {
17		randomInt, _ := rand.Int(rand.Reader, bigInt)
18		randStr += string(BASE[randomInt.Int64()])
19	}
20	return randStr
21}

签名:

 1package main
 2
 3import (
 4	"crypto/hmac"
 5	"crypto/sha256"
 6	"encoding/hex"
 7	"fmt"
 8)
 9
10func CreateAccessSign(appKey, appSecret, rand, timestamp string) string {
11	// 有随机数的签名
12	plain := fmt.Sprintf("appKey=%s&appSecret=%s&rand=%s&timestamp=%s", appKey, appSecret, rand, timestamp)
13	key := []byte(appSecret)
14	h := hmac.New(sha256.New, key)
15	h.Write([]byte(plain))
16	return hex.EncodeToString(h.Sum(nil))
17}

Python

 1import hmac, time, random
 2from hashlib import sha256
 3
 4
 5def hmac_sha256(key, plain):
 6    sign = hmac.new(key, plain, sha256).hexdigest()
 7    return sign
 8
 9
10if __name__ == '__main__':
11    appKey = '[接入方 appKey]'      # 应用 appKey
12    appSecret = '[接入方 appSecret]'  # 应用密钥
13    rand = random.randint(100000, 900000)  # 6位随机数
14    timestamp = int(time.time())
15    raw = "appKey={}&appSecret={}&rand={}&timestamp={}".format(appKey, appSecret, rand, timestamp)
16    sign = hmac_sha256(appSecret.encode(), raw.encode())
17    print(sign)

JS

1var appKey = '[接入方 appKey]';
2var appSecret = '[接入方 appSecret]';
3var timestamp = Math.round(new Date().getTime() / 1000);  // 获取秒数时间戳
4var rand = Math.round(100000 + Math.random() * 900000);  // 6位随机数
5var magic = `appKey=${appKey}&appSecret=${appSecret}&rand=${rand}&timestamp=${timestamp}`;
6var signature = CryptoJS.HmacSHA256(magic, appSecret).toString();

四、请求示例

以 Java 为例,提供一个携带签名的接口访问示例:

 1@Test
 2public void signTest() throws Exception {
 3    // 应用 appKey
 4    String appKey = "[接入方 appKey]";
 5    // 应用密钥
 6    String appSecret = "[接入方 appSecret]";
 7    // 随机数
 8    String rand = randStr();
 9    // 时间戳
10    String timestamp = timestampStr();
11    // 生成签名
12    String sign = getSign(appKey, appSecret, rand, timestamp);
13    // 使用 http 访问接口
14    // 请求头携带 x-appKey、x-signature、x-timestamp、x-rand 参数
15    OkHttpClient client = new OkHttpClient();
16    String json = "";
17    String url = "http://your-server/api/v1/xxx";
18    RequestBody body = RequestBody.create(json, MediaType.get("application/json; charset=utf-8"));
19    Request request = new Request.Builder()
20            .addHeader("x-appKey", appKey)
21            .addHeader("x-signature", sign)
22            .addHeader("x-timestamp", timestamp)
23            .addHeader("x-rand", rand)
24            .url(url)
25            .post(body)
26            .build();
27    try (Response response = client.newCall(request).execute()) {
28        String bodyStr = response.body().string();
29        // 打印响应结果
30        System.out.println(bodyStr);
31    }
32}

注意各语言的差异点:示例中的 rand 有的是 4~6 位字母数字串、有的是 6 位纯数字,接入时以服务方约定的规则为准,签名前 plain 串的拼装顺序必须与服务端完全一致。