App 授权签名机制

更新时间:2024-07-02 06:13:08下载pdf

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

签名算法

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

API 类型 令牌管理 API 普通业务 API
适用范围 获取令牌和刷新令牌 API 除令牌管理接口以外的其他 API
签名算法 str = client_id + t + nonce + identifier + stringToSign
sign = HMAC-SHA256(str, secret).toUpperCase()
str = client_id + access_token + t + nonce + identifier + stringToSign
sign = HMAC-SHA256(str, secret).toUpperCase()
字段说明
  • client_id:授权密钥对 key。
  • t:13 位标准时间戳。
  • nonce:开发者生成的 UUID,结合时间戳可以防重复,非必填。
  • identifier:App 授权自定义的签名内容。授权类型为 Android 时,需要将 SHA1 和 Application ID 拼接起来组成 identifier,中间无需分隔符。对于其它类型,直接使用自定义的 Bundle Identifier。
  • stringToSign:签名字符串。生成方式见下方 stringToSign 签名字符串。
  • secret:授权密钥对 value。
  • access_token:访问令牌。通过授权密钥对获取。
字段说明 将 client_id 与当前请求的 13 位标准时间戳( t )、nonce、identifier、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 的值为 2,body 参数为空。

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

请求头结构如下:

参数 取值
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=2

结构解析:

App 授权签名机制

拼接待签名字符串

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

1KAD46OrT9HafiKdsXeg15889257780005138cc3a9033d69856923fd07b491173GET
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
area_id:29a33e8796834b1efa6
call_id:8afdb70ab2ed11eb85290242ac130003

/v1.0/token?grant_type=2

结构解析:

App 授权签名机制

HMAC SHA256 创建摘要

哈希摘要:

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

/v1.0/token?grant_type=2,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

结构解析:

App 授权签名机制

拼接待签名字符串

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

1KAD46OrT9HafiKdsXeg3f4eda2bdec17232f67c0b188af3eec115889257780005138cc3a9033d69856923fd07b491173GET
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
area_id:29a33e8796834b1efa6
call_id:8afdb70ab2ed11eb85290242ac130003

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

结构解析:

App 授权签名机制

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 的拼接换行符,两个换行符拼接在一起,就会导致中间空出一行。