全部產品
Search
文件中心

API Gateway:使用摘要簽名認證方式調用API

更新時間:Jul 13, 2024

發布的API如果使用摘要簽名認證方式(APP Key和APP Secret),用戶端在調用API時,需要使用簽名金鑰組請求內容進行簽名計算,並將簽名同步傳輸給伺服器端進行簽名驗證。API Gateway提供的SDK內建了簽名實現,您只需要將簽名密鑰配置在SDK中,即可實現發起攜帶正確簽名的請求。如果您需要自己在用戶端實現簽名計算過程,可以參考本文檔。

1 摘要簽名認證方式

API Gateway提供前端簽名及驗簽能力,前端簽名及驗簽主要兩點用途:

  • 驗證用戶端請求的合法性,確認請求中攜帶授權後的AK產生的簽名;

  • 防止請求資料在網路傳輸過程中被篡改。

API的擁有者可以在API Gateway控制台的應用管理頁面產生APP,每個APP會攜帶一對簽名密鑰(APP Key和APP Secret),API擁有者將API授權給指定的APP(APP可以是API擁有者頒發或者API調用者所有)後,API調用者就可以用APP的簽名密鑰來調用相關的API了。

用戶端調用 API 時,需要使用已授權簽名金鑰組請求內容的關鍵資料進行加密簽名計算,並且將APP Key和加密後產生的字串放在請求的 Header 傳輸給API Gateway,API Gateway會讀取請求中的APP Key的頭資訊,並且根據APP Key的值查詢到對應的APP Secret的值,使用APP Secret對收到的請求中的關鍵資料進行簽名計算,並且使用自己的產生的簽名和用戶端傳上來的簽名進行比對,來驗證簽名的正確性。只有簽名驗證通過的請求才會發送給後端服務,否則API Gateway會認為該請求為非法請求,直接返回錯誤應答。

API Gateway只會驗證安全認證類型為“阿里雲APP”類型的API請求的簽名,其他安全類型的API請求不會驗證簽名。

2 使用SDK調用

簽名計算的詳細實現可以參考API Gateway提供的SDK,在API Gateway控制台的下面兩個頁面中可以下載Java/Android/Objective-C的帶源碼的SDK:

  • 開放API-SDK/文檔自動產生

  • 調用API-已授權的API的SDK

具體可參考使用SDK調用API

3 摘要簽名認證方式原理說明

3.1簽名產生流程和認證流程概述

3.1.1 前置條件

API的調用方需要在調用API之前擷取到已經授權給對應API的簽名金鑰組:

  • APP Key

  • APP Secret

被調用的API的安全認證類型為“阿里雲APP”。

3.1.2 用戶端產生簽名

用戶端產生簽名一共分三步處理:

  1. 從原始請求中提取關鍵資料,得到一個用來簽名的簽名串;

  2. 使用密碼編譯演算法加APP Secret對關鍵資料簽名串進行加密處理,得到簽名;

  3. 將簽名所相關的所有頭加入到原始HTTP請求中,得到最終HTTP請求。

3.1.3 伺服器端驗證簽名

伺服器驗證用戶端簽名一共分四步處理:

  1. 從接收到的請求中提取關鍵資料,得到一個用來簽名的簽名串;

  2. 從接收到的請求中讀取APP Key,通過APP Key查詢到對應的APP Secret;

  3. 使用密碼編譯演算法和APP Secret對關鍵資料簽名串進行加密處理,得到簽名;

  4. 從接收到的請求中讀取用戶端簽名,對比伺服器端簽名和用戶端簽名的一致性。

3.2 產生與傳遞簽名

3.2.1 提取簽名串

用戶端需要從HTTP請求中提取出關鍵資料,組合成一個簽名串,產生的簽名串的格式如下:

HTTPMethod
Accept
Content-MD5
Content-Type
Date
Headers
PathAndParameters

以上7個欄位構成整個簽名串,欄位之間使用\n間隔,如果Headers為空白,則不需要加\n,其他欄位如果為空白都需要保留\n。簽名大小寫敏感。下面介紹下每個欄位的擷取規則:

  • HTTPMethod:HTTP的方法,全部大寫,比如POST

  • Accept:請求中的Accept頭的值,可為空白。建議顯式設定 Accept Header。當 Accept 為空白時,部分 HTTP 用戶端會給 Accept 設定預設值為 */*,導致簽名校正失敗。

  • Content-MD5:請求中的Content-MD5頭的值,可為空白。只有在請求存在Body且Body為非Form形式時才計算Content-MD5頭,下面是Java的Content-MD5值的參考計算方式:

String content-MD5 = Base64.encodeBase64(MD5(bodyStream.getbytes("UTF-8")));

  • Content-Type:請求中的Content-Type頭的值,可為空白

重要

當用戶端使用微信小程式來傳輸檔案或是底層一些邏輯導致簽名的Content-Type異常時,可以增加一個自訂Content-Type頭"X-Ca-Signed-Content-Type:multipart/form-data",用戶端使用這個頭進行簽名,網關也會用這個頭進行簽名。

  • Date:請求中的Date頭的值,可為空白

  • Headers:使用者可以選取指定的header參與簽名,關於header的簽名串拼接方式有以下規則:

    • 參與簽名計算的Header的Key按照字典排序後使用如下方式拼接

    HeaderKey1 + ":" + HeaderValue1 + "\n" +
    HeaderKey2 + ":" + HeaderValue2 + "\n" +
    ...
    HeaderKeyN + ":" + HeaderValueN + "\n"
    • 某個Header的Value為空白,則使用HeaderKey+":"+"\n"參與簽名,需要保留Key和英文冒號。

    • 所有參與簽名的Header的Key的集合使用英文逗號分割放到Key為X-Ca-Signature-Headers的Header中;

    • 以下Header不參與Header簽名計算:X-Ca-Signature、X-Ca-Signature-Headers、Accept、Content-MD5、Content-Type、Date。

  • PathAndParameters這個欄位包含Path,Query和Form中的所有參數,具體組織形式如下:

    Path + "?" + Key1 + "=" + Value1 + "&" + Key2 + "=" + Value2 + ... "&" + KeyN + "=" + ValueN
    • Query和Form參數對的Key按照字典排序後使用上面的方式拼接;

    • Query和Form參數為空白時,則直接使用Path,不需要添加?;

    • 參數的Value為空白時只保留Key參與簽名,等號不需要再加入簽名;

    • Query和Form存在數組參數時(key相同,value不同的參數) ,取第一個Value參與簽名計算。

下面我們看一個例子,這裡有一個普通的HTTP請求:

POST /http2test/test?param1=test HTTP/1.1
host:api.aliyun.com
accept:application/json; charset=utf-8
ca_version:1
content-type:application/x-www-form-urlencoded; charset=utf-8
x-ca-timestamp:1525872629832
date:Wed, 09 May 2018 13:30:29 GMT+00:00
user-agent:ALIYUN-ANDROID-DEMO
x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
content-length:33
username=xiaoming&password=123456789

產生的正確簽名串為:

POST
application/json; charset=utf-8
application/x-www-form-urlencoded; charset=utf-8
Wed, 09 May 2018 13:30:29 GMT+00:00
x-ca-key:203753385
x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
x-ca-signature-method:HmacSHA256
x-ca-timestamp:1525872629832
/http2test/test?param1=test&password=123456789&username=xiaoming

3.2.2 計算簽名

用戶端從HTTP請求中提取出關鍵資料群組裝成簽名串後,需要對簽名串進行加密及編碼處理,形成最終的簽名,目前API Gateway支援以下兩種密碼編譯演算法:

  • HmacSHA256

  • HmacSHA1

具體的加密形式如下,其中stringToSign是提取出來的簽名串,secret是APP Secret:

Mac hmacSha256 = Mac.getInstance("HmacSHA256");
byte[] appSecretBytes = appSecret.getBytes(Charset.forName("UTF-8"));
hmacSha256.init(new SecretKeySpec(appSecretBytes, 0, appSecretBytes.length, "HmacSHA256"));
byte[] md5Result = hmacSha256.doFinal(stringToSign.getBytes(Charset.forName("UTF-8")));
String signature = Base64.encodeBase64String(md5Result);

Mac hmacSha1 = Mac.getInstance("HmacSHA1");
byte[] appSecretBytes = appSecret.getBytes(Charset.forName("UTF-8"));
hmacSha1.init(new SecretKeySpec(appSecretBytes, 0, appSecretBytes.length, "HmacSHA1"));
byte[] md5Result = hmacSha1.doFinal(stringToSign.getBytes(Charset.forName("UTF-8")));
String signature = Base64.encodeBase64String(md5Result);

抽象一下就是將串(StringToSign)使用UTF-8解碼後得到Byte數組,然後使用密碼編譯演算法對Byte數組進行加密,然後使用Base64演算法進行編碼,形成最終的簽名。

3.2.3 傳輸簽名

用戶端需要將以下四個Header放在HTTP請求中傳輸給API Gateway,進行簽名校正:

  • x-ca-key:取值APP Key,必選;

  • x-ca-signature-method:簽名演算法,取值HmacSHA256或者HmacSHA1,可選,預設值為HmacSHA256;

  • X-Ca-Signature-Headers:所有簽名頭的Key的集合,使用英文逗號分隔,可選;

  • X-Ca-Signature:簽名,必選;

下面是攜帶簽名的整個HTTP請求的樣本:

POST /http2test/test?param1=test HTTP/1.1
host:api.aliyun.com
accept:application/json; charset=utf-8
ca_version:1
content-type:application/x-www-form-urlencoded; charset=utf-8
x-ca-timestamp:1525872629832
date:Wed, 09 May 2018 13:30:29 GMT+00:00
user-agent:ALIYUN-ANDROID-DEMO
x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
x-ca-key:203753385
x-ca-signature-method:HmacSHA256
x-ca-signature-headers:x-ca-timestamp,x-ca-key,x-ca-nonce,x-ca-signature-method
x-ca-signature:xfX+bZxY2yl7EB/qdoDy9v/uscw3Nnj1pgoU+Bm6xdM=
content-length:33
username=xiaoming&password=123456789

3.3 簽名排錯方法

API Gateway簽名校正失敗時,會將服務端的簽名串(StringToSign)放到HTTP Response的Header中返回到用戶端,Key為:X-Ca-Error-Message,使用者只需要將本地計算的簽名串(StringToSign)與服務端返回的簽名串進行對比即可找到問題;

如果服務端與用戶端的StringToSign一致請檢查用於簽名計算的APP Secret是否正確;

因為HTTP Header中無法表示換行,因此StringToSign中的分行符號都被替換成#。

errorMessage:  Invalid Signature, Server StringToSign:`GET#application/json##application/json##X-Ca-Key:200000#X-Ca-Timestamp:1589458000000#/app/v1/config/keys?keys=TEST`

說明伺服器的簽名是:

GET
application/json
application/json
X-Ca-Key:200000
X-Ca-Timestamp:1589458000000
/app/v1/config/keys?keys=TEST