簽名機(jī)制
為保證API的安全調(diào)用,在調(diào)用API時(shí)阿里云會(huì)對(duì)每個(gè)API請(qǐng)求通過簽名(Signature)進(jìn)行身份驗(yàn)證。無論使用HTTP還是HTTPS協(xié)議提交請(qǐng)求,都需要在請(qǐng)求中包含簽名信息。此文檔描述了如何計(jì)算簽名,提供了Java、Python、Go的代碼示例。
概述
RPC API要按以下格式在API請(qǐng)求的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:唯一隨機(jī)數(shù),用于防止網(wǎng)絡(luò)重放攻擊。用戶在不同請(qǐng)求間要使用不同的隨機(jī)數(shù)值,建議使用通用唯一識(shí)別碼(Universally Unique Identifier, UUID)。
Signature:使用AccessKey Secret對(duì)請(qǐng)求進(jìn)行對(duì)稱加密后生成的簽名。
簽名算法遵循RFC2104 HMAC-SHA1規(guī)范,使用AccessSecret對(duì)編碼、排序后的整個(gè)請(qǐng)求串計(jì)算HMAC值作為簽名。簽名的元素是請(qǐng)求自身的一些參數(shù),由于每個(gè)API請(qǐng)求內(nèi)容不同,所以簽名的結(jié)果也不盡相同??蓞⒖急疚牡牟僮鞑襟E,計(jì)算簽名值。(詳細(xì)簽名規(guī)則請(qǐng)參考簽名機(jī)制說明)
String signature = Base64(HMAC_SHA1(AccessSecret + "&", UTF_8_Encoding_Of(stringToSign)))
步驟一:構(gòu)造待簽名字符串
使用請(qǐng)求參數(shù)構(gòu)造規(guī)范化的請(qǐng)求字符串(CanonicalizedQueryString)。
按照參數(shù)名稱的字典順序?qū)φ?qǐng)求中所有的請(qǐng)求參數(shù)(包括公共請(qǐng)求參數(shù)和接口的自定義參數(shù),但不包括公共請(qǐng)求參數(shù)中的Signature參數(shù))進(jìn)行排序。
說明這些參數(shù)是請(qǐng)求URI中的參數(shù)部分,即URI中“?”之后由“&”連接的部分。詳見示例部分。
對(duì)排序之后的請(qǐng)求參數(shù)的名稱和值分別用UTF-8字符集進(jìn)行URL編碼。編碼規(guī)則請(qǐng)參考下表。
字符
編碼方式
A-Z、a-z和0-9以及“-”、“_”、“.”和“~”
不編碼
其它字符
編碼成 %XY 的格式,其中 XY 是字符對(duì)應(yīng)ASCII碼的16進(jìn)制表示。例如英文的雙引號(hào)(””)對(duì)應(yīng)的編碼為 %22
擴(kuò)展的UTF-8字符
編碼成 %XY%ZA…的格式
英文空格
編碼成 %20,而不是加號(hào)(+)。
注:該編碼方式和一般采用的 application/x-www-form-urlencoded MIME格式編碼算法(例如Java標(biāo)準(zhǔn)庫(kù)中的 java.net.URLEncoder的實(shí)現(xiàn))存在區(qū)別。編碼時(shí)可以先用標(biāo)準(zhǔn)庫(kù)的方式進(jìn)行編碼,然后把編碼后的字符串中的加號(hào)(+)替換成 %20,星號(hào)(*)替換成 %2A,%7E替換回波浪號(hào)(~),即可得到上述規(guī)則描述的編碼字符串。本算法可以用下面的percentEncode方法來實(shí)現(xiàn):
private static String percentEncode(String value) throws UnsupportedEncodingException { return value != null ? URLEncoder.encode(value, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null; }
將編碼后的參數(shù)名稱和值用英文等號(hào)(=)進(jìn)行連接。
將等號(hào)連接得到的參數(shù)組合按步驟 1 排好的順序依次使用“&”符號(hào)連接,即得到規(guī)范化請(qǐng)求字符串。
將第一步構(gòu)造的規(guī)范化字符串按照下面的規(guī)則構(gòu)造成待簽名的字符串。
String StringToSign = HTTPMethod + "&" + percentEncode(“/”) + "&" + percentEncode(CanonicalizedQueryString)
其中:
HttpMethod 是提交請(qǐng)求用的HTTP方法,例如GET。注意:如果計(jì)算簽名時(shí)使用GET方法,最后請(qǐng)求時(shí)也需要使用GET方法,否則會(huì)導(dǎo)致簽名錯(cuò)誤。
percentEncode("/") 是對(duì)字符 “/” 進(jìn)行編碼得到的值,即 %2F。
percentEncode(CanonicalizedQueryString) 是對(duì)步驟1中構(gòu)造的規(guī)范化請(qǐng)求字符串(CanonicalizedQueryString)按照步驟1.b描述的編碼規(guī)則編碼后得到的字符串。
步驟二:計(jì)算簽名值
根據(jù)RFC2104的定義,計(jì)算待簽名字符串(StringToSign)的HMAC值。
說明計(jì)算簽名時(shí)使用的Key就是您持有的AccessKey Secret并加上一個(gè) “&” 字符(ASCII:38), 使用的哈希算法是SHA1。
按照Base64編碼規(guī)則把上面的HMAC值編碼成字符串,即得到簽名值(Signature)。
將得到的簽名值作為Signature參數(shù)添加到請(qǐng)求參數(shù)中。
說明得到的簽名值在作為最后的請(qǐng)求參數(shù)值提交時(shí),需要按照RFC3986的規(guī)則進(jìn)行URL編碼。
示例
以 DescribeRegions API 為例,假設(shè)使用的AccessKey Id 為 testid, AccessKey Secret為 testsecret。 簽名前的請(qǐng)求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
請(qǐng)求參數(shù)按照參數(shù)名稱的字典順序排序后為:
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
根據(jù)步驟一第1部分描述,對(duì)排序之后的請(qǐng)求參數(shù)的名稱和值分別用UTF-8字符集進(jìn)行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
根據(jù)步驟一第2部分描述,構(gòu)造的待簽名字符串(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規(guī)則編碼的簽名(Signature)參數(shù)加入到URL請(qǐng)求中,得到的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
代碼示例
您可以參考下方的代碼示例構(gòu)造最終的請(qǐng)求鏈接(不需要安裝第三方庫(kù))。
下面的代碼僅作為示例,請(qǐng)勿在生產(chǎn)環(huán)境中使用。
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中的參數(shù)部分
URL u = new URL(url);
String query = u.getQuery();
// 獲取范化的請(qǐng)求字符串
String canonicalString = Arrays.stream(query.split("&"))
.map(s -> s.split("="))
// 去掉不合法的空參數(shù)(例如: https://example?Url=&AccessKeyId=)
.filter(arr -> arr != null && arr.length > 1)
// 根據(jù)請(qǐng)求參數(shù)的字典順序排序
.sorted(Comparator.comparing(arr -> arr[0]))
// 按照 RFC3986 規(guī)則編碼參數(shù)名稱、參數(shù)值
.map(arr -> String.format("%s=%s", percentEncode(arr[0]), percentEncode(urlDecode(arr[1]))))
// 用"&"拼接起來編碼后的參數(shù)
.reduce((s1, s2) -> s1 + "&" + s2)
.orElse("");
// 將規(guī)范化字符串拼接成待簽名的字符串
String stringToSign = httpMethod + "&" + percentEncode("/") + "&" + percentEncode(canonicalString);
// 把 AccessKeySecret加上"&"構(gòu)成 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 編碼規(guī)則生成最后的簽名字符串
String signature = Base64.getEncoder().encodeToString(hashBytes);
return signature;
}
/**
* 獲取公共請(qǐng)求參數(shù)。公共請(qǐng)求參數(shù)詳見文檔:http://bestwisewords.com/document_detail/145074.html
*
* @param accessKeyId 您的AccessKeyId。如何獲取AccessKeyId請(qǐng)參考:http://bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5fXEVz0
* @return 公共請(qǐng)求參數(shù)組成的字典
*/
private static Map<String, String> getCommonParameters(String accessKeyId) {
return new HashMap<String, String>() {
{
put("Action", "RecognizeGeneral"); // 調(diào)用的接口名稱,此處以 RecognizeGeneral 為例
put("Version", "2021-07-07"); // API版本。OCR的固定值:2021-07-07
put("Format", "JSON"); // 指定接口返回?cái)?shù)據(jù)的格式,可以選擇 JSON 或者 XML
put("AccessKeyId", accessKeyId); // 您的AccessKeyId
put("SignatureNonce", UUID.randomUUID().toString()); // 簽名唯一隨機(jī)數(shù),每次調(diào)用不可重復(fù)
put("Timestamp", Instant.now().with(ChronoField.NANO_OF_SECOND, 0).toString()); // 需要Java8及以上版本。如果您需要使用更低版本Java,請(qǐng)更換獲取時(shí)間戳的方法
put("SignatureMethod", "HMAC-SHA1"); // 簽名方式。目前為固定值 HMAC-SHA1
put("SignatureVersion", "1.0"); // 簽名方式。目前為固定值 1.0
}
};
}
/**
* 使用傳圖片URL方法請(qǐng)求接口示例。以 RecognizeGeneral 接口為例。
*/
private static void getDemo() throws Exception {
String endpoint = "ocr-api.cn-hangzhou.aliyuncs.com";
// 如何獲取AccessKeyId、AccessKeySecret請(qǐng)參考文檔:http://bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5GvMJ25
String accessKeyId = "";
String accessKeySecret = "";
// 獲取公共請(qǐng)求參數(shù)
Map<String, String> parametersMap = getCommonParameters(accessKeyId);
// 添加業(yè)務(wù)參數(shù)(不同的接口參數(shù)有差異,此處以RecognizeGeneral為例,Url參數(shù)為圖片鏈接)
parametersMap.put("Url", "https://example.png");
// 初始化請(qǐng)求URL
StringBuilder urlBuilder = new StringBuilder("https://" + endpoint + "/?");
// 把業(yè)務(wù)參數(shù)拼接到請(qǐng)求鏈接中
for (Map.Entry<String, String> entry : parametersMap.entrySet()) {
// entry.getValue() 可能出現(xiàn)"&"等符號(hào)。 需要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 規(guī)則編碼簽名,并添加到最終的請(qǐng)求鏈接上
url += String.format("&Signature=%s", percentEncode(signature));
// 通過GET方式請(qǐng)求接口并打印識(shí)別結(jié)果,以 RecognizeGeneral 為例
String result = doGet(url);
System.out.println(result);
}
/**
* 識(shí)別本地文件代碼示例。以 RecognizeGeneral 接口為例。
*/
private static void postDemo() throws Exception {
String endpoint = "ocr-api.cn-hangzhou.aliyuncs.com";
// 如何獲取AccessKeyId、AccessKeySecret請(qǐng)參考文檔:http://bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5GvMJ25
String accessKeyId = "";
String accessKeySecret = "";
// 獲取公共請(qǐng)求參數(shù)
Map<String, String> parametersMap = getCommonParameters(accessKeyId);
// 初始化請(qǐng)求URL
StringBuilder urlBuilder = new StringBuilder("https://" + endpoint + "/?");
// 把業(yè)務(wù)參數(shù)拼接到請(qǐng)求鏈接中
for (Map.Entry<String, String> entry : parametersMap.entrySet()) {
// entry.getValue() 可能出現(xiàn)"&"等符號(hào)。 需要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 規(guī)則編碼簽名,并添加到最終的請(qǐng)求鏈接上
url += String.format("&Signature=%s", percentEncode(signature));
// 本地文件
String fileName = "/home/example.png";
// 通過POST請(qǐng)求接口并打印識(shí)別結(jié)果,以 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時(shí)間
"""
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中的參數(shù)部分
queries = parse_qs(urlparse(url).query)
# 按照請(qǐng)求參數(shù)字典順序排序
keys = sorted(queries.keys())
# 初始化規(guī)范化的請(qǐng)求字符串
canonicalized_query_string = ""
# 生成 規(guī)范化的請(qǐng)求字符串
for k in keys:
# 按照 RFC3986 規(guī)則編碼參數(shù)名稱
quoted_param = percentEncode(k)
# 按照 RFC3986 規(guī)則編碼參數(shù)值
quoted_value = percentEncode(queries[k][0])
# 把編碼后的參數(shù)名稱和值用英文等號(hào)(=)拼接起來,然后用 "&" 連接
canonicalized_query_string += '&%s=%s' % (quoted_param, quoted_value)
# 去掉開頭的 "&"
canonicalized_query_string = canonicalized_query_string[1:]
# 將規(guī)范化字符串拼接成待簽名的字符串
string_to_sign = http_method + '&' + percentEncode('/') + '&' + percentEncode(canonicalized_query_string)
# 把 AccessKeySecret加上"&"構(gòu)成 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 編碼規(guī)則生成最后的簽名字符串
signature = base64.b64encode(hash_bytes).decode('utf-8')
return signature
def get_common_parameters(access_key_id):
"""獲取公共請(qǐng)求參數(shù)。公共請(qǐng)求參數(shù)詳見文檔:http://bestwisewords.com/document_detail/145074.html
Args:
您的AccessKeyId。如何獲取AccessKeyId請(qǐng)參考:http://bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5fXEVz0
Returns:
公共請(qǐng)求參數(shù)組成的字典
"""
return {
"Action": "RecognizeGeneral", # 調(diào)用的接口名稱,此處以 RecognizeGeneral 為例
"Version": "2021-07-07", # API版本。OCR的固定值:2021-07-07
"Format": "JSON", # 指定接口返回?cái)?shù)據(jù)的格式,可以選擇 JSON 或者 XML
"AccessKeyId": access_key_id, # 您的AccessKeyId
"SignatureNonce": uuid.uuid4(), # 簽名唯一隨機(jī)數(shù)
"Timestamp": datetime.utcnow().replace(tzinfo=UTC()).strftime('%Y-%m-%dT%H:%M:%SZ'), # 請(qǐng)求的時(shí)間戳。按照ISO8601標(biāo)準(zhǔn)表示,并需要使用UTC時(shí)間,格式為yyyy-MM-ddTHH:mm:ssZ
"SignatureMethod": "HMAC-SHA1", # 簽名方式。目前為固定值 HMAC-SHA1
"SignatureVersion": "1.0" # 簽名方式。目前為固定值 1.0
}
def get_request_url():
"""獲取完整的請(qǐng)求URL。
"""
endpoint = "ocr-api.cn-hangzhou.aliyuncs.com"
# 如何獲取AccessKeyId、AccessKeySecret請(qǐng)參考文檔:http://bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5GvMJ25
access_key_id = "" # 您的AccessKeyId
access_key_secret = "" # 您的AccessKeySecret
# 獲取公共請(qǐng)求參數(shù)
parameters = get_common_parameters(access_key_id)
# 添加業(yè)務(wù)參數(shù)(不同的接口參數(shù)有差異,此處以RecognizeGeneral為例,Url參數(shù)為圖片鏈接)
parameters['Url'] = "https://example.png"
# 把業(yè)務(wù)參數(shù)拼接到請(qǐng)求鏈接中
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 規(guī)則編碼簽名,并添加到最終的請(qǐng)求鏈接上
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 生成隨機(jī)字符串,作為 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 獲取公共請(qǐng)求參數(shù)。公共請(qǐng)求參數(shù)詳見文檔:http://bestwisewords.com/document_detail/145074.html
func getCommonParameters(accessKeyId string) map[string]string {
return map[string]string{
"Action": "RecognizeGeneral", // 調(diào)用的接口名稱,此處以 RecognizeGeneral 為例
"Version": "2021-07-07", // API版本。OCR的固定值:2021-07-07
"Format": "JSON", // 指定接口返回?cái)?shù)據(jù)的格式,可以選擇 JSON 或者 XML
"AccessKeyId": accessKeyId, // 您的AccessKeyId
"SignatureNonce": uuid(), // 簽名唯一隨機(jī)數(shù)
"Timestamp": time.Now().UTC().Format("2006-01-02T15:04:05.000Z"), //請(qǐng)求的時(shí)間戳。按照ISO8601標(biāo)準(zhǔn)表示,并需要使用UTC時(shí)間,格式為yyyy-MM-ddTHH:mm:ssZ。示例:2018-01-01T12:00:00Z表示北京時(shí)間2018年01月01日20點(diǎn)00分00秒。
"SignatureMethod": "HMAC-SHA1", // 簽名方式。目前為固定值 HMAC-SHA1
"SignatureVersion": "1.0", // 簽名方式。目前為固定值 1.0
}
}
// getSignature 獲取簽名
func getSignature(urlString, secret, httpMethod string) string {
// 解析url中的參數(shù)部分
u, err := url.Parse(urlString)
if err != nil {
panic(err)
}
rawQuery := u.RawQuery
// 把url的請(qǐng)求參數(shù)名稱記錄到 slice 中
queryMap, err := url.ParseQuery(rawQuery)
if err != nil {
panic(err)
}
keys := make([]string, 0)
for k := range queryMap {
keys = append(keys, k)
}
// 按照請(qǐng)求參數(shù)字典順序排序
sort.Strings(keys)
// 初始化規(guī)范化的請(qǐng)求字符串
canonicalString := ""
for i, k := range keys {
canonicalString += fmt.Sprintf("%s=%s", percentEncode(k), percentEncode(queryMap[k][0]))
if i < len(keys)-1 {
canonicalString += "&"
}
}
// 將規(guī)范化字符串拼接成待簽名的字符串
stringToSign := httpMethod + "&" + percentEncode("/") + "&" + percentEncode(canonicalString)
// 把 AccessKeySecret加上"&"構(gòu)成 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 編碼規(guī)則生成最后的簽名字符串
signature := base64.StdEncoding.EncodeToString(b)
return signature
}
// getRequestUrl 獲取完整的請(qǐng)求URL
func getRequestUrl() string {
endpoint := "ocr-api.cn-hangzhou.aliyuncs.com"
// 如何獲取AccessKeyId、AccessKeySecret請(qǐng)參考文檔:http://bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5GvMJ25
accessKeyId := "" // 您的AccessKeyId
accessKeySecret := "" // 您的AccessKeySecret
// 獲取公共請(qǐng)求參數(shù)
parameters := getCommonParameters(accessKeyId)
// 添加業(yè)務(wù)參數(shù)(不同的接口參數(shù)有差異,此處以RecognizeGeneral為例,Url參數(shù)為圖片鏈接)
parameters["Url"] = "https://example.png"
// 把業(yè)務(wù)參數(shù)拼接到請(qǐng)求鏈接中
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 規(guī)則編碼簽名,并添加到最終的請(qǐng)求鏈接上
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, "~");
};
// 獲取公共請(qǐng)求參數(shù)。公共請(qǐng)求參數(shù)詳見文檔:http://bestwisewords.com/document_detail/145074.html
const getCommonParameters = (accessKeyId) => {
return {
Action: "RecognizeGeneral", // 調(diào)用的接口名稱,此處以 RecognizeGeneral 為例
Version: "2021-07-07", // API版本。OCR的固定值:2021-07-07
Format: "JSON", // 指定接口返回?cái)?shù)據(jù)的格式,可以選擇 JSON 或者 XML
AccessKeyId: accessKeyId, // 您的AccessKeyId
SignatureNonce: crypto.randomBytes(16).toString("hex"), // 簽名唯一隨機(jī)數(shù)
Timestamp: new Date().toISOString(), // 請(qǐng)求的時(shí)間戳。按照ISO8601標(biāo)準(zhǔn)表示,并需要使用UTC時(shí)間,格式為yyyy-MM-ddTHH:mm:ssZ
SignatureMethod: "HMAC-SHA1", // 簽名方式。目前為固定值 HMAC-SHA1
SignatureVersion: "1.0", // 簽名方式。目前為固定值 1.0
};
};
// 獲取簽名
const getSignature = (urlString, secret, httpMethod) => {
// 解析url中的參數(shù)部分
const query = url.parse(urlString, true).query;
// 按照請(qǐng)求參數(shù)字典順序排序
keys = Object.keys(query).sort();
// 初始化規(guī)范化的請(qǐng)求字符串
canonicalString = "";
for (const k of keys) {
// 按照 RFC3986 規(guī)則編碼參數(shù)名稱
let encodedParam = percentEncode(k);
// 按照 RFC3986 規(guī)則編碼參數(shù)值
let encodedValue = percentEncode(query[k]);
// 把編碼后的參數(shù)名稱和值用英文等號(hào)(=)拼接起來,然后用 "&" 連接
canonicalString += `&${encodedParam}=${encodedValue}`;
}
// 去掉開頭的 "&"
canonicalString = canonicalString.substring(1);
// 將規(guī)范化字符串拼接成待簽名的字符串
let stringToSign =
httpMethod +
"&" +
percentEncode("/") +
"&" +
percentEncode(canonicalString);
// 把 AccessKeySecret加上"&"構(gòu)成 HMAC-SHA1 算法的key
secret += "&";
// HMAC-SHA1 編碼后的bytes
let res = crypto.createHmac("sha1", secret).update(stringToSign).digest();
// 按照 base64 編碼規(guī)則生成最后的簽名字符串
let signature = res.toString("base64");
return signature;
};
// 獲取完整的請(qǐng)求URL
const getRequestUrl = () => {
const endpoint = "ocr-api.cn-hangzhou.aliyuncs.com";
// 如何獲取AccessKeyId、AccessKeySecret請(qǐng)參考文檔:http://bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5GvMJ25
const accessKeyId = ""; // 您的AccessKeyId
const accessKeySecret = ""; // 您的AccessKeySecret
// 獲取公共請(qǐng)求參數(shù)
let parameters = getCommonParameters(accessKeyId);
// 添加業(yè)務(wù)參數(shù)(不同的接口參數(shù)有差異,此處以RecognizeGeneral為例,Url參數(shù)為圖片鏈接)
parameters["Url"] = "https://example.png";
// 把業(yè)務(wù)參數(shù)拼接到請(qǐng)求鏈接中
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 規(guī)則編碼簽名,并添加到最終的請(qǐng)求鏈接上
requestUrl += `&Signature=${percentEncode(signature)}`;
return requestUrl;
};
const res = getRequestUrl();
console.log(res);