為保證API的安全調用,在調用API時阿里云會對每個API請求通過簽名(Signature)進行身份驗證。無論使用HTTP還是HTTPS協議提交請求,都需要在請求中包含簽名信息。此文檔描述了如何計算簽名,提供了Java、Python、Go的代碼示例。
概述
RPC API要按以下格式在API請求的Query中增加簽名(Signature)。
https://Endpoint/?SignatureVersion=1.0&SignatureMethod=HMAC-SHA1&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&Signature=CT9X0VtwR86fNWSnsc6v8YGOjuE%3D&
其中
SignatureMethod:簽名方式,目前支持HMAC-SHA1。
SignatureVersion:簽名算法版本,目前版本是 1.0。
SignatureNonce:唯一隨機數,用于防止網絡重放攻擊。用戶在不同請求間要使用不同的隨機數值,建議使用通用唯一識別碼(Universally Unique Identifier, UUID)。
Signature:使用AccessKey Secret對請求進行對稱加密后生成的簽名。
簽名算法遵循RFC2104 HMAC-SHA1規范,使用AccessSecret對編碼、排序后的整個請求串計算HMAC值作為簽名。簽名的元素是請求自身的一些參數,由于每個API請求內容不同,所以簽名的結果也不盡相同。可參考本文的操作步驟,計算簽名值。(詳細簽名規則請參考簽名機制說明)
String signature = Base64(HMAC_SHA1(AccessSecret + "&", UTF_8_Encoding_Of(stringToSign)))
步驟一:構造待簽名字符串
使用請求參數構造規范化的請求字符串(CanonicalizedQueryString)。
按照參數名稱的字典順序對請求中所有的請求參數(包括公共請求參數和接口的自定義參數,但不包括公共請求參數中的Signature參數)進行排序。
說明這些參數是請求URI中的參數部分,即URI中“?”之后由“&”連接的部分。詳見示例部分。
對排序之后的請求參數的名稱和值分別用UTF-8字符集進行URL編碼。編碼規則請參考下表。
字符
編碼方式
A-Z、a-z和0-9以及“-”、“_”、“.”和“~”
不編碼
其它字符
編碼成 %XY 的格式,其中 XY 是字符對應ASCII碼的16進制表示。例如英文的雙引號(””)對應的編碼為 %22
擴展的UTF-8字符
編碼成 %XY%ZA…的格式
英文空格
編碼成 %20,而不是加號(+)。
注:該編碼方式和一般采用的 application/x-www-form-urlencoded MIME格式編碼算法(例如Java標準庫中的 java.net.URLEncoder的實現)存在區別。編碼時可以先用標準庫的方式進行編碼,然后把編碼后的字符串中的加號(+)替換成 %20,星號(*)替換成 %2A,%7E替換回波浪號(~),即可得到上述規則描述的編碼字符串。本算法可以用下面的percentEncode方法來實現:
private static String percentEncode(String value) throws UnsupportedEncodingException { return value != null ? URLEncoder.encode(value, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null; }
將編碼后的參數名稱和值用英文等號(=)進行連接。
將等號連接得到的參數組合按步驟 1 排好的順序依次使用“&”符號連接,即得到規范化請求字符串。
將第一步構造的規范化字符串按照下面的規則構造成待簽名的字符串。
String StringToSign = HTTPMethod + "&" + percentEncode(“/”) + "&" + percentEncode(CanonicalizedQueryString)
其中:
HttpMethod 是提交請求用的HTTP方法,例如GET。注意:如果計算簽名時使用GET方法,最后請求時也需要使用GET方法,否則會導致簽名錯誤。
percentEncode("/") 是對字符 “/” 進行編碼得到的值,即 %2F。
percentEncode(CanonicalizedQueryString) 是對步驟1中構造的規范化請求字符串(CanonicalizedQueryString)按照步驟1.b描述的編碼規則編碼后得到的字符串。
步驟二:計算簽名值
根據RFC2104的定義,計算待簽名字符串(StringToSign)的HMAC值。
說明計算簽名時使用的Key就是您持有的AccessKey Secret并加上一個 “&” 字符(ASCII:38), 使用的哈希算法是SHA1。
按照Base64編碼規則把上面的HMAC值編碼成字符串,即得到簽名值(Signature)。
將得到的簽名值作為Signature參數添加到請求參數中。
說明得到的簽名值在作為最后的請求參數值提交時,需要按照RFC3986的規則進行URL編碼。
示例
以 DescribeRegions API 為例,假設使用的AccessKey Id 為 testid, AccessKey Secret為 testsecret。 簽名前的請求URL如下:
http://ecs.aliyuncs.com/?Timestamp=2016-02-23T12:46:24Z&Format=XML&AccessKeyId=testid&Action=DescribeRegions&SignatureMethod=HMAC-SHA1&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&Version=2014-05-26&SignatureVersion=1.0
請求參數按照參數名稱的字典順序排序后為:
AccessKeyId=testid&Action=DescribeRegions&Format=XML&SignatureMethod=HMAC-SHA1&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&SignatureVersion=1.0&Timestamp=2016-02-23T12:46:24Z&Version=2014-05-26
根據步驟一第1部分描述,對排序之后的請求參數的名稱和值分別用UTF-8字符集進行URL編碼后為:
AccessKeyId=testid&Action=DescribeRegions&Format=XML&SignatureMethod=HMAC-SHA1&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&SignatureVersion=1.0&Timestamp=2016-02-23T12%3A46%3A24Z&Version=2014-05-26
根據步驟一第2部分描述,構造的待簽名字符串(stringToSign) 為(使用GET方法):
GET&%2F&AccessKeyId%3Dtestid%26Action%3DDescribeRegions%26Format%3DXML%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf%26SignatureVersion%3D1.0%26Timestamp%3D2016-02-23T12%253A46%253A24Z%26Version%3D2014-05-26
按照步驟二中的方法得到的簽名(Signature) 為:
OLeaidS1JvxuMvnyHOwuJ+uX5qY=
最后按照RFC3986規則編碼的簽名(Signature)參數加入到URL請求中,得到的URL為:
http://ecs.aliyuncs.com/?Timestamp=2016-02-23T12:46:24Z&Format=XML&AccessKeyId=testid&Action=DescribeRegions&SignatureMethod=HMAC-SHA1&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&Version=2014-05-26&SignatureVersion=1.0&Signature=OLeaidS1JvxuMvnyHOwuJ%2BuX5qY%3D
代碼示例
您可以參考下方的代碼示例構造最終的請求鏈接(不需要安裝第三方庫)。
下面的代碼僅作為示例,請勿在生產環境中使用。
package demo;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.temporal.ChronoField;
import java.util.Arrays;
import java.util.Base64;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class Demo {
private static String percentEncode(String value) {
try {
return value != null ? URLEncoder.encode(value, "UTF-8")
.replace("+", "%20")
.replace("*", "%2A")
.replace("%7E", "~") : null;
} catch (UnsupportedEncodingException ignore) {
return value;
}
}
private static String urlDecode(String value) {
try {
return URLDecoder.decode(value, "UTF-8");
} catch (UnsupportedEncodingException ignore) {
return value;
}
}
private static String readFromInputStream(InputStream source) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(source))) {
return reader.lines().collect(Collectors.joining("\n"));
} catch (IOException ignore) {
return "";
}
}
private static String doPost(String url, String fileName) {
HttpURLConnection urlConnection = null;
try {
URL postUrl = new URL(url);
urlConnection = (HttpURLConnection) postUrl.openConnection();
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
urlConnection.setRequestMethod("POST");
urlConnection.setUseCaches(false);
urlConnection.setRequestProperty("Content-Type", "application/octet-stream");
Path filePath = Paths.get(fileName);
if (!filePath.toFile().isFile()) {
throw new FileNotFoundException(String.format("file:%s not found", fileName));
}
Files.copy(filePath, urlConnection.getOutputStream());
return readFromInputStream(urlConnection.getInputStream());
} catch (FileNotFoundException e) {
return e.getMessage();
} catch (IOException e) {
if (urlConnection != null) {
return readFromInputStream(urlConnection.getErrorStream());
}
return e.getMessage();
}
}
private static String doGet(String url) {
HttpURLConnection urlConnection = null;
try {
URL postUrl = new URL(url);
urlConnection = (HttpURLConnection) postUrl.openConnection();
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
urlConnection.setRequestMethod("GET");
urlConnection.setUseCaches(false);
return readFromInputStream(urlConnection.getInputStream());
} catch (IOException e) {
if (urlConnection != null) {
return readFromInputStream(urlConnection.getErrorStream());
}
return e.getMessage();
}
}
public static String getSignature(String url, String secret, String httpMethod) throws Exception {
// 解析url中的參數部分
URL u = new URL(url);
String query = u.getQuery();
// 獲取范化的請求字符串
String canonicalString = Arrays.stream(query.split("&"))
.map(s -> s.split("="))
// 去掉不合法的空參數(例如: https://example?Url=&AccessKeyId=)
.filter(arr -> arr != null && arr.length > 1)
// 根據請求參數的字典順序排序
.sorted(Comparator.comparing(arr -> arr[0]))
// 按照 RFC3986 規則編碼參數名稱、參數值
.map(arr -> String.format("%s=%s", percentEncode(arr[0]), percentEncode(urlDecode(arr[1]))))
// 用"&"拼接起來編碼后的參數
.reduce((s1, s2) -> s1 + "&" + s2)
.orElse("");
// 將規范化字符串拼接成待簽名的字符串
String stringToSign = httpMethod + "&" + percentEncode("/") + "&" + percentEncode(canonicalString);
// 把 AccessKeySecret加上"&"構成 HMAC-SHA1 算法的key
secret += "&";
// HMAC-SHA1 編碼后的bytes
SecretKey secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(secretKey);
byte[] hashBytes = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
// 按照 base64 編碼規則生成最后的簽名字符串
String signature = Base64.getEncoder().encodeToString(hashBytes);
return signature;
}
/**
* 獲取公共請求參數。公共請求參數詳見文檔:http://m.bestwisewords.com/document_detail/145074.html
*
* @param accessKeyId 您的AccessKeyId。如何獲取AccessKeyId請參考:http://m.bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5fXEVz0
* @return 公共請求參數組成的字典
*/
private static Map<String, String> getCommonParameters(String accessKeyId) {
return new HashMap<String, String>() {
{
put("Action", "RecognizeGeneral"); // 調用的接口名稱,此處以 RecognizeGeneral 為例
put("Version", "2021-07-07"); // API版本。OCR的固定值:2021-07-07
put("Format", "JSON"); // 指定接口返回數據的格式,可以選擇 JSON 或者 XML
put("AccessKeyId", accessKeyId); // 您的AccessKeyId
put("SignatureNonce", UUID.randomUUID().toString()); // 簽名唯一隨機數,每次調用不可重復
put("Timestamp", Instant.now().with(ChronoField.NANO_OF_SECOND, 0).toString()); // 需要Java8及以上版本。如果您需要使用更低版本Java,請更換獲取時間戳的方法
put("SignatureMethod", "HMAC-SHA1"); // 簽名方式。目前為固定值 HMAC-SHA1
put("SignatureVersion", "1.0"); // 簽名方式。目前為固定值 1.0
}
};
}
/**
* 使用傳圖片URL方法請求接口示例。以 RecognizeGeneral 接口為例。
*/
private static void getDemo() throws Exception {
String endpoint = "ocr-api.cn-hangzhou.aliyuncs.com";
// 如何獲取AccessKeyId、AccessKeySecret請參考文檔:http://m.bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5GvMJ25
String accessKeyId = "";
String accessKeySecret = "";
// 獲取公共請求參數
Map<String, String> parametersMap = getCommonParameters(accessKeyId);
// 添加業務參數(不同的接口參數有差異,此處以RecognizeGeneral為例,Url參數為圖片鏈接)
parametersMap.put("Url", "https://example.png");
// 初始化請求URL
StringBuilder urlBuilder = new StringBuilder("https://" + endpoint + "/?");
// 把業務參數拼接到請求鏈接中
for (Map.Entry<String, String> entry : parametersMap.entrySet()) {
// entry.getValue() 可能出現"&"等符號。 需要encode
urlBuilder.append(String.format("%s=%s", entry.getKey(), URLEncoder.encode(entry.getValue(), "UTF-8")))
.append('&');
}
// 去掉最后的"&"
String url = urlBuilder.substring(0, urlBuilder.length() - 1);
// 獲取簽名
String signature = getSignature(url, accessKeySecret, "GET");
// 按照 RFC3986 規則編碼簽名,并添加到最終的請求鏈接上
url += String.format("&Signature=%s", percentEncode(signature));
// 通過GET方式請求接口并打印識別結果,以 RecognizeGeneral 為例
String result = doGet(url);
System.out.println(result);
}
/**
* 識別本地文件代碼示例。以 RecognizeGeneral 接口為例。
*/
private static void postDemo() throws Exception {
String endpoint = "ocr-api.cn-hangzhou.aliyuncs.com";
// 如何獲取AccessKeyId、AccessKeySecret請參考文檔:http://m.bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5GvMJ25
String accessKeyId = "";
String accessKeySecret = "";
// 獲取公共請求參數
Map<String, String> parametersMap = getCommonParameters(accessKeyId);
// 初始化請求URL
StringBuilder urlBuilder = new StringBuilder("https://" + endpoint + "/?");
// 把業務參數拼接到請求鏈接中
for (Map.Entry<String, String> entry : parametersMap.entrySet()) {
// entry.getValue() 可能出現"&"等符號。 需要encode
urlBuilder.append(String.format("%s=%s", entry.getKey(), URLEncoder.encode(entry.getValue(), "UTF-8")))
.append('&');
}
// 去掉最后的"&"
String url = urlBuilder.substring(0, urlBuilder.length() - 1);
// 獲取簽名
String signature = getSignature(url, accessKeySecret, "POST");
// 按照 RFC3986 規則編碼簽名,并添加到最終的請求鏈接上
url += String.format("&Signature=%s", percentEncode(signature));
// 本地文件
String fileName = "/home/example.png";
// 通過POST請求接口并打印識別結果,以 RecognizeGeneral 為例
String result = doPost(url, fileName);
System.out.println(result);
}
public static void main(String[] args) throws Exception {
// getDemo();
// postDemo();
}
}
# encoding=utf-8
try:
from urllib.parse import quote, urlparse, parse_qs
except:
from urllib import quote
from urlparse import urlparse, parse_qs
import hmac
import base64
import hashlib
import uuid
from datetime import datetime, tzinfo, timedelta
class UTC(tzinfo):
"""表示UTC時間
"""
def tzname(self, dt):
return "UTC"
def utcoffset(self, dt):
return timedelta(0)
def dst(self, dt):
return timedelta(0)
def percentEncode(s):
return quote(s.encode('utf-8'), safe='~')
def get_signature(url, secret, http_method):
# 解析url中的參數部分
queries = parse_qs(urlparse(url).query)
# 按照請求參數字典順序排序
keys = sorted(queries.keys())
# 初始化規范化的請求字符串
canonicalized_query_string = ""
# 生成 規范化的請求字符串
for k in keys:
# 按照 RFC3986 規則編碼參數名稱
quoted_param = percentEncode(k)
# 按照 RFC3986 規則編碼參數值
quoted_value = percentEncode(queries[k][0])
# 把編碼后的參數名稱和值用英文等號(=)拼接起來,然后用 "&" 連接
canonicalized_query_string += '&%s=%s' % (quoted_param, quoted_value)
# 去掉開頭的 "&"
canonicalized_query_string = canonicalized_query_string[1:]
# 將規范化字符串拼接成待簽名的字符串
string_to_sign = http_method + '&' + percentEncode('/') + '&' + percentEncode(canonicalized_query_string)
# 把 AccessKeySecret加上"&"構成 HMAC-SHA1 算法的key
secret += '&'
# HMAC-SHA1 編碼后的bytes
hash_bytes = hmac.new(secret.encode('utf-8'), string_to_sign.encode('utf-8'), digestmod=hashlib.sha1).digest()
# 按照 base64 編碼規則生成最后的簽名字符串
signature = base64.b64encode(hash_bytes).decode('utf-8')
return signature
def get_common_parameters(access_key_id):
"""獲取公共請求參數。公共請求參數詳見文檔:http://m.bestwisewords.com/document_detail/145074.html
Args:
您的AccessKeyId。如何獲取AccessKeyId請參考:http://m.bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5fXEVz0
Returns:
公共請求參數組成的字典
"""
return {
"Action": "RecognizeGeneral", # 調用的接口名稱,此處以 RecognizeGeneral 為例
"Version": "2021-07-07", # API版本。OCR的固定值:2021-07-07
"Format": "JSON", # 指定接口返回數據的格式,可以選擇 JSON 或者 XML
"AccessKeyId": access_key_id, # 您的AccessKeyId
"SignatureNonce": uuid.uuid4(), # 簽名唯一隨機數
"Timestamp": datetime.utcnow().replace(tzinfo=UTC()).strftime('%Y-%m-%dT%H:%M:%SZ'), # 請求的時間戳。按照ISO8601標準表示,并需要使用UTC時間,格式為yyyy-MM-ddTHH:mm:ssZ
"SignatureMethod": "HMAC-SHA1", # 簽名方式。目前為固定值 HMAC-SHA1
"SignatureVersion": "1.0" # 簽名方式。目前為固定值 1.0
}
def get_request_url():
"""獲取完整的請求URL。
"""
endpoint = "ocr-api.cn-hangzhou.aliyuncs.com"
# 如何獲取AccessKeyId、AccessKeySecret請參考文檔:http://m.bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5GvMJ25
access_key_id = "" # 您的AccessKeyId
access_key_secret = "" # 您的AccessKeySecret
# 獲取公共請求參數
parameters = get_common_parameters(access_key_id)
# 添加業務參數(不同的接口參數有差異,此處以RecognizeGeneral為例,Url參數為圖片鏈接)
parameters['Url'] = "https://example.png"
# 把業務參數拼接到請求鏈接中
url = "https://%s/?%s" % (endpoint, '&'.join('%s=%s' % (k, v) for k, v in parameters.items()))
# 獲取簽名(GET方法)
signature = get_signature(url, access_key_secret, 'GET')
# 按照 RFC3986 規則編碼簽名,并添加到最終的請求鏈接上
url += "&Signature=" + percentEncode(signature)
return url
if __name__ == '__main__':
request_url = get_request_url()
print(request_url)
package main
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"fmt"
"net/url"
"sort"
"strings"
"time"
)
// uuid 生成隨機字符串,作為 SignatureNonce
func uuid() string {
b := make([]byte, 16)
_, err := rand.Read(b)
if err != nil {
panic(err)
}
return fmt.Sprintf("%X-%X-%X-%X-%X", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}
func percentEncode(s string) string {
s = url.QueryEscape(s)
s = strings.ReplaceAll(s, "+", "%20")
s = strings.ReplaceAll(s, "*", "%2A")
s = strings.ReplaceAll(s, "%7E", "~")
return s
}
// getCommonParameters 獲取公共請求參數。公共請求參數詳見文檔:http://m.bestwisewords.com/document_detail/145074.html
func getCommonParameters(accessKeyId string) map[string]string {
return map[string]string{
"Action": "RecognizeGeneral", // 調用的接口名稱,此處以 RecognizeGeneral 為例
"Version": "2021-07-07", // API版本。OCR的固定值:2021-07-07
"Format": "JSON", // 指定接口返回數據的格式,可以選擇 JSON 或者 XML
"AccessKeyId": accessKeyId, // 您的AccessKeyId
"SignatureNonce": uuid(), // 簽名唯一隨機數
"Timestamp": time.Now().UTC().Format("2006-01-02T15:04:05.000Z"), //請求的時間戳。按照ISO8601標準表示,并需要使用UTC時間,格式為yyyy-MM-ddTHH:mm:ssZ。示例:2018-01-01T12:00:00Z表示北京時間2018年01月01日20點00分00秒。
"SignatureMethod": "HMAC-SHA1", // 簽名方式。目前為固定值 HMAC-SHA1
"SignatureVersion": "1.0", // 簽名方式。目前為固定值 1.0
}
}
// getSignature 獲取簽名
func getSignature(urlString, secret, httpMethod string) string {
// 解析url中的參數部分
u, err := url.Parse(urlString)
if err != nil {
panic(err)
}
rawQuery := u.RawQuery
// 把url的請求參數名稱記錄到 slice 中
queryMap, err := url.ParseQuery(rawQuery)
if err != nil {
panic(err)
}
keys := make([]string, 0)
for k := range queryMap {
keys = append(keys, k)
}
// 按照請求參數字典順序排序
sort.Strings(keys)
// 初始化規范化的請求字符串
canonicalString := ""
for i, k := range keys {
canonicalString += fmt.Sprintf("%s=%s", percentEncode(k), percentEncode(queryMap[k][0]))
if i < len(keys)-1 {
canonicalString += "&"
}
}
// 將規范化字符串拼接成待簽名的字符串
stringToSign := httpMethod + "&" + percentEncode("/") + "&" + percentEncode(canonicalString)
// 把 AccessKeySecret加上"&"構成 HMAC-SHA1 算法的key
secret += "&"
// HMAC-SHA1 編碼后的bytes
h := hmac.New(sha1.New, []byte(secret))
_, err = h.Write([]byte(stringToSign))
if err != nil {
panic(err)
}
b := h.Sum(nil)
// 按照 base64 編碼規則生成最后的簽名字符串
signature := base64.StdEncoding.EncodeToString(b)
return signature
}
// getRequestUrl 獲取完整的請求URL
func getRequestUrl() string {
endpoint := "ocr-api.cn-hangzhou.aliyuncs.com"
// 如何獲取AccessKeyId、AccessKeySecret請參考文檔:http://m.bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5GvMJ25
accessKeyId := "" // 您的AccessKeyId
accessKeySecret := "" // 您的AccessKeySecret
// 獲取公共請求參數
parameters := getCommonParameters(accessKeyId)
// 添加業務參數(不同的接口參數有差異,此處以RecognizeGeneral為例,Url參數為圖片鏈接)
parameters["Url"] = "https://example.png"
// 把業務參數拼接到請求鏈接中
url := "https://" + endpoint + "/?"
for k, v := range parameters {
url += fmt.Sprintf("%s=%s&", k, v)
}
// 去掉url最后的"&"
url = url[:len(url)-1]
// 獲取簽名
signature := getSignature(url, accessKeySecret, "GET")
// 按照 RFC3986 規則編碼簽名,并添加到最終的請求鏈接上
url += "&Signature=" + percentEncode(signature)
return url
}
func main() {
url := getRequestUrl()
fmt.Println(url)
}
const url = require("url");
const crypto = require("crypto");
const percentEncode = (s) => {
return encodeURIComponent(s)
.replace(/\+/g, "%20")
.replace(/\*/g, "%2A")
.replace(/%7E/g, "~");
};
// 獲取公共請求參數。公共請求參數詳見文檔:http://m.bestwisewords.com/document_detail/145074.html
const getCommonParameters = (accessKeyId) => {
return {
Action: "RecognizeGeneral", // 調用的接口名稱,此處以 RecognizeGeneral 為例
Version: "2021-07-07", // API版本。OCR的固定值:2021-07-07
Format: "JSON", // 指定接口返回數據的格式,可以選擇 JSON 或者 XML
AccessKeyId: accessKeyId, // 您的AccessKeyId
SignatureNonce: crypto.randomBytes(16).toString("hex"), // 簽名唯一隨機數
Timestamp: new Date().toISOString(), // 請求的時間戳。按照ISO8601標準表示,并需要使用UTC時間,格式為yyyy-MM-ddTHH:mm:ssZ
SignatureMethod: "HMAC-SHA1", // 簽名方式。目前為固定值 HMAC-SHA1
SignatureVersion: "1.0", // 簽名方式。目前為固定值 1.0
};
};
// 獲取簽名
const getSignature = (urlString, secret, httpMethod) => {
// 解析url中的參數部分
const query = url.parse(urlString, true).query;
// 按照請求參數字典順序排序
keys = Object.keys(query).sort();
// 初始化規范化的請求字符串
canonicalString = "";
for (const k of keys) {
// 按照 RFC3986 規則編碼參數名稱
let encodedParam = percentEncode(k);
// 按照 RFC3986 規則編碼參數值
let encodedValue = percentEncode(query[k]);
// 把編碼后的參數名稱和值用英文等號(=)拼接起來,然后用 "&" 連接
canonicalString += `&${encodedParam}=${encodedValue}`;
}
// 去掉開頭的 "&"
canonicalString = canonicalString.substring(1);
// 將規范化字符串拼接成待簽名的字符串
let stringToSign =
httpMethod +
"&" +
percentEncode("/") +
"&" +
percentEncode(canonicalString);
// 把 AccessKeySecret加上"&"構成 HMAC-SHA1 算法的key
secret += "&";
// HMAC-SHA1 編碼后的bytes
let res = crypto.createHmac("sha1", secret).update(stringToSign).digest();
// 按照 base64 編碼規則生成最后的簽名字符串
let signature = res.toString("base64");
return signature;
};
// 獲取完整的請求URL
const getRequestUrl = () => {
const endpoint = "ocr-api.cn-hangzhou.aliyuncs.com";
// 如何獲取AccessKeyId、AccessKeySecret請參考文檔:http://m.bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5GvMJ25
const accessKeyId = ""; // 您的AccessKeyId
const accessKeySecret = ""; // 您的AccessKeySecret
// 獲取公共請求參數
let parameters = getCommonParameters(accessKeyId);
// 添加業務參數(不同的接口參數有差異,此處以RecognizeGeneral為例,Url參數為圖片鏈接)
parameters["Url"] = "https://example.png";
// 把業務參數拼接到請求鏈接中
let requestUrl = `https://${endpoint}/?`;
for (const key in parameters) {
requestUrl += `${key}=${parameters[key]}&`;
}
requestUrl = requestUrl.substring(0, requestUrl.length - 1);
// 獲取簽名
const signature = getSignature(requestUrl, accessKeySecret, "GET");
// 按照 RFC3986 規則編碼簽名,并添加到最終的請求鏈接上
requestUrl += `&Signature=${percentEncode(signature)}`;
return requestUrl;
};
const res = getRequestUrl();
console.log(res);