1. 概述
1.1 版本
FineDataLink 版本 | 功能說明 |
---|---|
4.0.29 | - |
1.2 應用場景
對資料安全性要求高的使用者,需要對發佈的 API 進行更高安全性的鑑權方式設定,提供更安全的認證方式,滿足更嚴格的安全要求。
1.3 功能說明
資料服務發佈的 API 支援基於 AK/SK 認證邏輯的摘要簽章認證方式,避免認證資訊和請求資訊在傳輸程式中被截獲和篡改,提升認證安全性。
FineDataLink 發佈的 API 採用 HMAC-SHA256 摘要算法計算簽章,驗籤邏輯為:
請求發起方(計算簽章,並在請求中帶上簽章) ---> 請求接收方(按請求參數計算簽章,並校驗簽章)
2. 前提條件
3. 簽章計算方法
3.1 JAVA 簽章算法程式碼
3.1.1 POST-application/json請求
根據下列範例程式碼,建立JAVA 檔案:
package kk;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.UUID;
public class k {
public static void main(String[] args) {
//修改為對應的AppSecret
String secretKey = "1bbe91b1-a39c-4742-9694-e126bcf9a3bd";
//請求方式,注意大寫
String httpMethod = "POST";
//POST請求需要寫contentType,GET請求此處寫空字串
String contentType = "application/json";
//API路徑。如果是get請求,需要帶上參數,例如xxx/xxx?a=1
String pathAndParameters = "a5ce6bb4-467b-46f2-8878-2132635973bb/87";
//body請求體的內容
String data = "{\"paging\":{\"pageSize\":10,\"pageNum\":1},\"params\":[]}";
//Nonce,自動生成
String nonce = String.valueOf(UUID.randomUUID());
//時間戳,自動生成
String timestamp = String.valueOf(System.currentTimeMillis());
//待簽章字串
String stringToSign = httpMethod +"\n"+
nonce +"\n"+
timestamp +"\n"+
pathAndParameters +"\n"+
contentType +"\n"+
md5(data);
//簽章
String signature = hmacSHA256(secretKey,stringToSign);
//拼出完整的Authorization
System.out.println("Authorization:\n"+"HMAC-SHA256 Signature="+signature+",Nonce="+nonce+",Timestamp="+timestamp);
}
/**
* 對字串data進行HmacSHA256簽章,以Base64的結果傳回
*
* @param secretKey 簽章金鑰
* @param data 待簽章字串
*/
public static String hmacSHA256(String secretKey, String data) {
try {
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
hmacSha256.init(secretKeySpec);
byte[] hashBytes = hmacSha256.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hashBytes);
} catch (Exception e) {
throw new RuntimeException("HmacSha error", e);
}
}
/**
* 對字串data計算md5摘要值,再進行Base64編碼,傳回編碼後的結果
*
* @param data 待計算的字串
*/
public static String md5(String data) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.reset();
md.update(data.getBytes(StandardCharsets.UTF_8));
String md5Result = String.format("%032x", new BigInteger(1, md.digest()));
return Base64.getEncoder().encodeToString(md5Result.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException("md5 error", e);
}
}
}
3.1.2 GET請求
package kk;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.UUID;
public class k {
public static void main(String[] args) {
//修改為對應的AppSecret
String secretKey = "a07eefc1-4b29-469a-8cb1-f68e3532d3a2";
//請求方式,注意大寫
String httpMethod = "GET";
//POST請求需要寫contentType,GET請求此處寫空字串
String contentType = "";
//API路徑。如果是get請求,需要帶上參數,例如xxx/xxx?a=1
String pathAndParameters = "a5ce6bb4-467b-46f2-8878-2132635973bb/dd?pageSize=10&pageNum=1";
//body請求體的內容
String data = "";
//Nonce,自動生成
String nonce = String.valueOf(UUID.randomUUID());
//時間戳,自動生成
String timestamp = String.valueOf(System.currentTimeMillis());
//待簽章字串
String stringToSign = httpMethod +"\n"+
nonce +"\n"+
timestamp +"\n"+
pathAndParameters +"\n"+
contentType +"\n"+
("".equals(data)?"":md5(data));
//簽章
String signature = hmacSHA256(secretKey,stringToSign);
//拼出完整的Authorization
System.out.println("Authorization:\n"+"HMAC-SHA256 Signature="+signature+",Nonce="+nonce+",Timestamp="+timestamp);
}
/**
* 對字串data進行HmacSHA256簽章,以Base64的結果傳回
*
* @param secretKey 簽章金鑰
* @param data 待簽章字串
*/
public static String hmacSHA256(String secretKey, String data) {
try {
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
hmacSha256.init(secretKeySpec);
byte[] hashBytes = hmacSha256.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hashBytes);
} catch (Exception e) {
throw new RuntimeException("HmacSha error", e);
}
}
/**
* 對字串data計算md5摘要值,再進行Base64編碼,傳回編碼後的結果
*
* @param data 待計算的字串
*/
public static String md5(String data) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.reset();
md.update(data.getBytes(StandardCharsets.UTF_8));
String md5Result = String.format("%032x", new BigInteger(1, md.digest()));
return Base64.getEncoder().encodeToString(md5Result.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException("md5 error", e);
}
}
}
3.1.3 POST-application/x-www-form-urlencoded請求
package kk;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.UUID;
public class k {
public static void main(String[] args) {
//修改為對應的AppSecret
String secretKey = "1bbe91b1-a39c-4742-9694-e126bcf9a3bd";
//請求方式,注意大寫
String httpMethod = "POST";
//POST請求需要寫contentType,GET請求此處寫空字串
String contentType = "application/x-www-form-urlencoded";
//API路徑。如果是get請求,需要帶上參數,例如xxx/xxx?a=1
String pathAndParameters = "a5ce6bb4-467b-46f2-8878-2132635973bb/87";
//body請求體的內容 body裏的鍵值對也要編碼
String data = "a=1&b=%E6%8C%AA%E5%A8%81";
//Nonce,自動生成
String nonce = String.valueOf(UUID.randomUUID());
//時間戳,自動生成
String timestamp = String.valueOf(System.currentTimeMillis());
//待簽章字串
String stringToSign = httpMethod +"\n"+
nonce +"\n"+
timestamp +"\n"+
pathAndParameters +"\n"+
contentType +"\n"+
md5(data);
//簽章
String signature = hmacSHA256(secretKey,stringToSign);
//拼出完整的Authorization
System.out.println("Authorization:\n"+"HMAC-SHA256 Signature="+signature+",Nonce="+nonce+",Timestamp="+timestamp);
}
/**
* 對字串data進行HmacSHA256簽章,以Base64的結果傳回
*
* @param secretKey 簽章金鑰
* @param data 待簽章字串
*/
public static String hmacSHA256(String secretKey, String data) {
try {
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
hmacSha256.init(secretKeySpec);
byte[] hashBytes = hmacSha256.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hashBytes);
} catch (Exception e) {
throw new RuntimeException("HmacSha error", e);
}
}
/**
* 對字串data計算md5摘要值,再進行Base64編碼,傳回編碼後的結果
*
* @param data 待計算的字串
*/
public static String md5(String data) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.reset();
md.update(data.getBytes(StandardCharsets.UTF_8));
String md5Result = String.format("%032x", new BigInteger(1, md.digest()));
return Base64.getEncoder().encodeToString(md5Result.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException("md5 error", e);
}
}
}
3.2 簽章算法原理
3.2.1 用戶端生成並傳入參數
用戶端需要傳入 Authorization 請求頭和 Content-Type 頭,使用 GET/POST 向服務端發起請求:
請求頭 | 內容 |
---|---|
Authorization | 算法名稱:Signature=Signature值, Nonce=Nonce隨機數, Timestamp=Timestamp時間戳 舉例: HMAC-SHA256 Signature=FiXSVh+oBzitw+HDypwlmB0PE+Z67pdXbQM7QyHA7qk=, Nonce=c967a237-cd6c-470e-906f-a8655461897, Timestamp=1686542039670 |
Content-Type | 根據請求的內容類型,填入Content-Type請求頭內容。 如果是GET請求頭不需要傳入 Content-Type。 |
其中生成 Signature 值作為「待簽章字串」需要的專案如下,其中標註字母的內容需要使用者根據自己的實際值進行取代:
字母 | 專案 | 說明 |
---|---|---|
A | AppSecret | 應用金鑰 在資料服務>應用列表,選擇摘要簽章,即複製 AppSecret,如下圖所示: |
B | HTTPMethod | 請求方法,目前支援傳入GET和POST。注:需要大寫。 |
Nonce | UUID,在生成簽章的 Java 程式碼中自動生成。 驗證時,服務端將會透過此 UUID 檢查近 5 分鐘內是否有重複請求,如果有重複請求,將進行攔截 | |
Timestamp | 13 位時間戳(1970-01-01 00:00:00開始到現在的毫秒數),在生成簽章的 Java 程式碼中自動生成 驗證時,服務端將會透過此時間戳,檢查此請求是否已過期,如果請求已過期,將進行攔截 | |
D | PathAndParameters | API 配置頁面配置的 API 路徑 如果是 get 請求,需要帶上參數,例如xxx/xxx?a=1 範例:http://192.168.5.175:8089/webroot/service/publish/<appid>/api01?a=1&b=2中的粗體部分,不包含首尾的斜槓 在 API 列表下的授權應用中複製 API 路徑,如下圖所示: |
C | Content-Type | BODY 的 MIME 類型 POST 請求需要寫 contentType,GET 請求此處寫空字串 |
E | String data | POST 請求時,在 BODY 中的參數字串
例如 JSON 格式的 Body,範例為: {"paging":{"pageSize":10,"pageNum":1},"params":[]} 對於以上請求體內容,先做 MD5 處理(匯出32位小寫字串),結果再進行 BASE64 編碼,得到最終的 Content-MD5 結果。 |
「待簽章字串」構建方式:
待簽章字串 = 「HTTPMethod」+ "\n" +「Nonce」+ "\n" +「Timestamp」+ "\n" +「PathAndParameters」+ "\n" +「Content-Type」+ "\n" +「Content-MD5」 注1:加號代表連接,實際是每個專案之間透過換行分隔,如果沒有對應專案,則對應內容用空字串代替,但是換行需要儲存 注2:如果沒有 BODY,那麼 Content-MD5 專案直接為空字串,不對空 BODY 做 MD5(也就是說,目前對於 GET 請求,Content-Type 和 Content-MD5 專案都會為空字串) |
待簽章字串生成 Signature 簽章,使用 HMAC-SHA256 加密算法。
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);
3.2.2 服務終止受請求
計算簽章方式同用戶端
校驗專案:
驗證專案 | 透過條件 |
---|---|
Timestamp | 與服務端當前時間相比,時間差在5min以內 |
Nonce | 服務端在5min內,沒有重複的Nonce |
Signature | 服務端計算的 Signature 與請求頭中的 Signature 完全一致 |
4. 簽章計算範例(以java為例)
4.1 用戶端生成簽章
參考本文 3.1 節修改程式碼中的指定內容:
例如使用者已經在 FineDataLink 中發佈請求為 POST application/json 的 API ,則需要準備以下資料,以備生成待簽章字串:
專案 | 說明 | JAVA檔案中需要修改的內容 |
---|---|---|
AppSecret | 應用金鑰 在資料服務>應用列表,選擇摘要簽章,即複製 AppSecret,如下圖所示: | ![]() |
HTTPMethod | 請求方法,目前支援傳入 GET 和 POST注:需要大寫 | ![]() |
Nonce | UUID,在生成簽章的 Java 程式碼中自動生成 驗證時,服務端將會透過此 UUID 檢查近 5 分鐘內是否有重複請求,如果有重複請求,將進行攔截 | |
Timestamp | 13 位時間戳(1970-01-01 00:00:00開始到現在的毫秒數),在生成簽章的 Java 程式碼中自動生成 驗證時,服務端將會透過此時間戳,檢查此請求是否已過期,如果請求已過期,將進行攔截 | |
PathAndParameters | API 配置頁面配置的 API 路徑 如果是 get 請求,需要帶上參數,例如xxx/xxx?a=1 範例:http://192.168.5.175:8089/webroot/service/publish/<appid>/api01?a=1&b=2中的粗體部分,不包含首尾的斜槓 在 API 列表下的授權應用中複製 API 路徑,如下圖所示: | ![]() |
Content-Type | BODY 的 MIME 類型。 POST 請求需要寫 contentType,GET 請求此處寫空字串 | |
Content-MD5 | POST請求時,在B ODY 中的參數字串。
例如 JSON 格式的 Body,範例為: {"paging":{"pageSize":10,"pageNum":1},"params":[]} 對於 json 格式的 Body,可在 API 生成介面複製 Body 值 對於以上請求體內容,先做 MD5 處理(匯出32位小寫字串),結果再進行 BASE64 編碼,得到最終的 Content-MD5 結果 | ![]() |
然後運作 JAVA 檔案,即可獲得 Authorization ,如下圖所示:
4.2 服務端獲取請求
此處以 POST JSON 請求為例,輸入 Authorization 認證和 Body 請求值,即可取出發佈的 API 資料,如下圖所示: