签名机制

更新时间:2023-10-11 09:20:00下载pdf

为保证数据安全,在发起 API 调用请求时,您需要提供签名进行账号验证。本文主要介绍如何生成 API 签名。

接入云开发 SDK 可以免除您生成 API 签名的繁琐过程。更多详情,请参考 服务端 SDK

签名算法

API 签名采用 HMAC SHA256 方法创建摘要。对于 令牌管理 API普通业务 API,签名算法稍有不同。

API 类型 令牌管理 API 普通业务 API
适用范围 获取令牌和刷新令牌 API 除令牌管理接口以外的其他 API
签名算法 str = client_id + t + nonce + stringToSign
sign = HMAC-SHA256(str, secret).toUpperCase()
str = client_id + access_token + t + nonce + stringToSign
sign = HMAC-SHA256(str, secret).toUpperCase()
字段说明
  • client_id:授权密钥对 key。
  • t:13 位标准时间戳。
  • nonce:开发者生成的 UUID,结合时间戳可以防重复,非必填。
  • stringToSign:签名字符串。生成方式见下方 stringToSign 签名字符串。
  • secret:授权密钥对 value。
  • access_token:访问令牌。通过授权密钥对获取。
字段说明 将 client_id 与当前请求的 13 位标准时间戳( t )、nonce、stringToSign 拼接成待签名字符串 str。再将 str 与 secret 进行 HMAC-SHA256 哈希摘要后转成大写,即可获取签名。

令牌管理 API 和普通业务 API 签名算法仅仅是在 str 生成逻辑上有所不同,后者比前者多了一个访问令牌(access_token)参数的拼接。

stringToSign 签名字符串

结构

stringToSign 签名字符串结构如下:

String stringToSign=
HTTPMethod + "\n" +
Content-SHA256 + "\n" +
Optional_Signature_key + "\n" +
URL

总共包含四部分组成:

  • HTTPMethod
  • Content-SHA256
  • Optional_Signature_key
  • URL

stringToSign 签名字符串是将这四部分通过换行符(\n)进行拼接得到。

HTTPMethod

HTTPMethod 指接口请求方法。如 GET、POST、PUT、DELETE 等。

Content-SHA256

Content-SHA256 指请求 Body 的 SHA256 值。只有当 Body 非 Form 表单时,才计算 SHA256。

计算方式如下:

String content-SHA256 = SHA256(bodyStream.getbytes("UTF-8")); //bodyStream 为字节数组

当 Body 为空时,也会进行加密,加密结果为 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

Optional_Signature_key

Optional_Signature_key 是指在 请求结构 中添加的、参与签名计算的字段(Key)和字段取值(Value)拼接的字符串。例如:

  1. 现有请求结构如下:

    client_id: 1KAD46OrT9HafiKd****
    sign: C5EFD19AD45E33A060C0BE47AEF65D975D54B2D70CBAA7A1ACA1A7D0E5C0****
    sign_method: HMAC-SHA256
    t: 1588925778000
    access_token: 3f4eda2bdec17232f67c0b188af3****
    nonce: 5138cc3a9033d69856923fd07b49****
    ...
    
  2. 为了提高安全性,指定两个开发者自定义的新字段(area_idreq_id)参与验签:

    area_id: 29a33e8796834b1****
    req_id: 8afdb70ab2ed11eb85290242ac13****
    
  3. 则新的请求结构如下:

    client_id: 1KAD46OrT9HafiKd****
    sign: C5EFD19AD45E33A060C0BE47AEF65D975D54B2D70CBAA7A1ACA1A7D0E5C0****
    sign_method: HMAC-SHA256
    t: 1588925778000
    access_token: 3f4eda2bdec17232f67c0b188af3****
    nonce: 5138cc3a9033d69856923fd07b49****
    ...
    
    // 新增
    Signature-Headers: area_id:req_id
    area_id: 29a33e8796834b1**** // 开发者自定义
    req_id: 8afdb70ab2ed11eb85290242ac13****	// 开发者自定义
    

    可以发现,以上示例将参与签名的请求结构中的字段拼接到一起,用半角冒号(:)分隔,即代码中的 area_id:req_id,作为 Signature-Headers 的值。

    Optional_Signature_key 的值就是对 Signature-Headers 值包含的所有的 Key 和 这些 Key 对应的值进行拼接得到的,拼接结构如下:

    String Optional_Signature_key =
    Signature-Headers-Key1 + ":" + Signature-Headers-Value1 + "\n" +
    Signature-Headers-Key2 + ":" + Signature-Headers-Value2 + "\n" +
    ...
    Signature-Headers-KeyN + ":" + Signature-Headers-ValueN + "\n"
    

    对应上示例中的即是:

    String Optional_Signature_key =
    area_id + ":" + 29a33e8796834b1**** + "\n" +
    req_id + ":" + 8afdb70ab2ed11eb85290242ac13**** + "\n"
    

URL

URL 指请求 Path 和表单参数拼接而成的请求路径。

  • URL 拼接:对表单参数按照字典对参数的 Key 进行按字母自然升序排序后,按照如下方法拼接。如果 Query 或 Form 参数为空,则 URL=Path,不需要添加半角问号(?)。

    String url =
    Path +
    "?" +
    Key1 + "=" + Value1 +
    "&" + Key2 + "=" + Value2 +
    "&" + Key3 +
    ...
    "&" + KeyN + "=" + ValueN
    

    例如:

    String url = /v1.0/iot-03/devices/87707085bcddc23a5fa3/logs?end_time=1657263936000&event_types=1&start_time=1657160836000
    
  • URL 参数排序:按参数的字母自然升序排序,字母相同时比对下一位。

    例如,有以下参数:

    • start_time=1657160836000
    • end_time=1657263936000
    • event_types=1

    排序后的规则为:

    • end_time=1657263936000

    • event_types=1

    • start_time=1657160836000

      end_timeevent_types 首字母相同,event_types 的第二个字段 v 大于 end_time 的第二个字母 n。所以 event_types 排在 end_time 后面。
      start_time 的首字段大于 end_timeevent_types 的首字母,所以排在 end_timeevent_types 之后。

令牌管理 API 示例

以获取 Token 接口为例,参数 grant_type 的值为 1,body 参数为空。

GET:/v1.0/token?grant_type=1

请求头结构如下:

参数 取值
method GET
client_id 1KAD46OrT9HafiKdsXeg
secret 4OHBOnWOqaEC1mWXOpVL3yV50s0qGSRC
t 1588925778000
sign_method HMAC-SHA256
nonce 5138cc3a9033d69856923fd07b491173
Signature-Headers area_id:call_id
area_id (参与签名的开发者自定义字段 29a33e8796834b1efa6
call_id (参与签名的开发者自定义字段 8afdb70ab2ed11eb85290242ac130003

拼接 stringToSign 签名字符串

首先,拼接 stringToSign 签名字符串:

GET
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
area_id:29a33e8796834b1efa6
call_id:8afdb70ab2ed11eb85290242ac130003

/v1.0/token?grant_type=1

结构解析:

签名机制

拼接待签名字符串

拿到 stringToSign 之后,就可以拼接待签名字符串:

1KAD46OrT9HafiKdsXeg15889257780005138cc3a9033d69856923fd07b491173GET
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
area_id:29a33e8796834b1efa6
call_id:8afdb70ab2ed11eb85290242ac130003

/v1.0/token?grant_type=1

结构解析:

签名机制

HMAC SHA256 创建摘要

哈希摘要:

HMAC-SHA256(1KAD46OrT9HafiKdsXeg15889257780005138cc3a9033d69856923fd07b491173GET
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
area_id:29a33e8796834b1efa6
call_id:8afdb70ab2ed11eb85290242ac130003

/v1.0/token?grant_type=1,4OHBOnWOqaEC1mWXOpVL3yV50s0qGSRC)
  • 哈希摘要后得到新字符串:

    9e48a3e93b302eeecc803c7241985d0a34eb944f40fb573c7b5c2a82158af13e

  • 转换成大写后得到签名:

    9E48A3E93B302EEECC803C7241985D0A34EB944F40FB573C7B5C2A82158AF13E

普通业务 API 示例

以获取用户列表 API 为例。参数 schema 的值为 apps,body 参数为空。

参数 取值
URL /v2.0/apps/schema/users
method GET
client_id 1KAD46OrT9HafiKdsXeg
secret 4OHBOnWOqaEC1mWXOpVL3yV50s0qGSRC
t 1588925778000
access_token 3f4eda2bdec17232f67c0b188af3eec1
sign_method HMAC-SHA256
nonce 5138cc3a9033d69856923fd07b491173
Signature-Headers area_id:call_id
area_id (参与签名的开发者自定义字段 29a33e8796834b1efa6
call_id (参与签名的开发者自定义字段 8afdb70ab2ed11eb85290242ac130003
page_no 1
page_size 50

拼接 stringToSign 签名字符串

首先,拼接 stringToSign 签名字符串:

GET
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
area_id:29a33e8796834b1efa6
call_id:8afdb70ab2ed11eb85290242ac130003

/v2.0/apps/schema/users?page_no=1&page_size=50

结构解析:

签名机制

拼接待签名字符串

拿到 stringToSign 之后,就可以拼接待签名字符串了:

1KAD46OrT9HafiKdsXeg3f4eda2bdec17232f67c0b188af3eec115889257780005138cc3a9033d69856923fd07b491173GET
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
area_id:29a33e8796834b1efa6
call_id:8afdb70ab2ed11eb85290242ac130003

/v2.0/apps/schema/users?page_no=1&page_size=50

结构解析:

签名机制

HMAC SHA256 创建摘要

哈希摘要:

HMAC-SHA256(1KAD46OrT9HafiKdsXeg3f4eda2bdec17232f67c0b188af3eec115889257780005138cc3a9033d69856923fd07b491173GET
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
area_id:29a33e8796834b1efa6
call_id:8afdb70ab2ed11eb85290242ac130003

/v2.0/apps/schema/users?page_no=1&page_size=50,4OHBOnWOqaEC1mWXOpVL3yV50s0qGSRC)
  • 哈希摘要后得到新字符串:

    ae4481c692aa80b25f3a7e12c3a5fd9bbf6251539dd78e565a1a72a508a88784

  • 转换成大写后得到签名:

    AE4481C692AA80B25F3A7E12C3A5FD9BBF6251539DD78E565A1A72A508A88784

HMAC SHA256 实现

Java 示例

下载 Java 代码示例

Golang 示例

下载 Golang 代码示例

Node.js 示例

下载 Node.js 代码示例

JavaScript 示例

/**
Run the code online with this jsfiddle. Dependent upon an open source js library calledhttp://code.google.com/p/crypto-js/.
**/

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/enc-base64.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/hmac-sha256.min.js"></script>

<script>
  var hash = CryptoJS.HmacSHA256("str", "secret");
  var hashInBase64 = hash.toString().toUpperCase();
  document.write(hashInBase64);
</script>

PHP 示例

/**
PHP has built in methods for hash_hmac (PHP 5) and base64_encode (PHP 4, PHP 5) resulting in no outside dependencies. Say what you want about PHP but they have the cleanest code for this example.
**/

$s = strtoupper(hash_hmac("sha256", "str", 'secret'));
echo var_dump($s);

C# 示例

using System;
using System.Security.Cryptography;

namespace Test
{
  public class MyHmac
  {
    public static string Encrypt(string str, string secret)
            {
                secret = secret ?? "";
                var encoding = new System.Text.UTF8Encoding();
                byte[] keyByte = encoding.GetBytes(secret);
                byte[] messageBytes = encoding.GetBytes(str);
                using (var hmacsha256 = new HMACSHA256(keyByte))
                {
                    byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
                    StringBuilder builder = new StringBuilder();
                    for (int i = 0; i < hashmessage.Length; i++)
                    {
                        builder.Append(hashmessage[i].ToString("x2"));
                    }
                    return builder.ToString().ToUpper();
                }
            }
  }
}

常见问题

如何验证加密后的签名?

本地开发时,可基于 Postman 软件预先查看加密后的签名结果。具体查询方法,请参考 验证签名结果

为什么 stringToSign 签名字符串中间会有一行空行?

因为 stringToSign 结构导致。stringToSign 是由 HTTPMethod、Content-SHA256、Headers 和 URL 中间用换行符连接而成。其结构如下:

String stringToSign=
HTTPMethod + "\n" +
Content-SHA256 + "\n" +
Headers + "\n" +
URL

Headers 的结构如下,结尾也有一个换行符:

String Headers =
HeaderKey1 + ":" + HeaderValue1 + "\n" +
HeaderKey2 + ":" + HeaderValue2 + "\n" +
...
HeaderKeyN + ":" + HeaderValueN + "\n"

所以,Headers 的结尾的换行符再加上 stringToSign 的拼接换行符,两个换行符拼接在一起,就会导致中间空出一行。