日本熟妇hd丰满老熟妇,中文字幕一区二区三区在线不卡 ,亚洲成片在线观看,免费女同在线一区二区

文檔

V3版本請求體&簽名機制

更新時間:
一鍵部署

V3版本通過公共請求頭設置接口必要的參數信息,在簽名機制的實現上屏蔽了接口風格的差異,更標準、更簡單。本文提供了詳細的指南,用于幫助您了解和實施阿里云SDK V3版的請求結構和簽名過程。您會了解如何構造標準的HTTP請求,以及如何使用正確的簽名算法來驗證請求的身份,確保傳輸的數據的完整性和安全性。如果您想自研阿里云OpenAPI的請求簽名,您可以參考本文。

HTTP 請求結構

一個完整的阿里云 OpenAPI 請求,包含以下部分。

名稱

是否必選

描述

示例值

協議

您可以查閱不同云產品的 API 參考文檔進行配置。支持通過HTTPHTTPS協議進行請求通信。為了獲得更高的安全性,推薦您使用HTTPS協議發送請求。取值范圍為https://或者 http://

https://

服務地址

即 Endpoint。您可以查閱不同云產品的服務接入地址文檔,查閱不同服務區域下的服務地址。

cs.aliyuncs.com

resource_URI_parameters(接口URL)

接口URL,包括接口路徑和位置在 path、 query的接口請求參數。

/clusters/{cluster_id}/triggers

RequestHeader(請求頭信息)

請求頭信息,通常包含API的版本、Host、Authorization等信息。后文將詳細說明。

x-acs-action

RequestBody

定義在 body 中的業務請求參數,建議您在阿里云 OpenAPI 開發者門戶進行試用。

cluster_id

HTTPMethod

請求使用的方法,ROA接口請求方法包括PUT、POST、GET、DELETE。

POST

RequestHeader(公共請求頭)

一個完整的阿里云 OpenAPI 請求,包含以下部分。

名稱

類型

是否必選

描述

示例值

x-acs-action

String

API的名稱。您可以訪問阿里云 OpenAPI 開發者門戶,搜索您想調用的 OpenAPI

RunInstances

x-acs-version

String

API 版本。您可以訪問阿里云 OpenAPI 開發者門戶,查看您調用 OpenAPI 對應的 API 版本

2014-05-26

Authorization

String

非匿名請求必須

用于驗證請求合法性的認證信息,格式為Authorization: SignatureAlgorithm Credential=AccessKeyId,SignedHeaders=SignedHeaders,Signature=Signature。其中SignatureAlgorithm為簽名加密方式,為ACS3-HMAC-SHA256。

Credential 為用戶的訪問密鑰ID。您可以在RAM 控制臺查看您的 AccessKeyId。如需創建 AccessKey,請參見創建AccessKeySignedHeaders為請求頭中包含的參與簽名字段鍵名,【說明】:除Authorization外的所有公共請求頭,只要存在必須被加入簽名。

Signature為請求簽名,取值參見簽名機制。

ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=e521358f7776c97df52e6b2891a8bc73026794a071b50c3323388c4e0df64804

x-acs-signature-nonce

String

簽名唯一隨機數。用于防止網絡重放攻擊,建議您每一次請求都使用不同的隨機數。

d410180a5abf7fe235dd9b74aca91fc0

x-acs-date

String

按照ISO 8601標準表示的UTC時間,格式為yyyy-MM-ddTHH:mm:ssZ,例如2018-01-01T12:00:00Z。值為請求發出前15分鐘內的時間。

2023-10-26T09:01:01Z

host

String

即服務地址,參見HTTP 請求結構

ecs.cn-shanghai.aliyuncs.com

x-acs-content-sha256

String

請求正文Hash摘要后再base-16編碼的結果,與HashedRequestPayload一致。

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

x-acs-security-token

String

STS認證必傳

為調用Assumerole接口返回值中SecurityToken的值。

接口請求構造

一個完整的接口請求構造如下:

HTTPMethod /resource_URI_parameters
RequestHeader
RequestBody

請求參數由公共請求頭和API自定義參數組成。公共請求頭中包含API版本號、身份驗證等信息。

  • HTTPMethod :請求使用的方法,包括PUT、POST、GET、DELETE。詳情請參看HTTP 請求結構

  • resource_URI_parameters:請求要調用的資源標識符,如/cluster。詳情請參看HTTP 請求結構

  • RequestHeader:請求頭信息,通常包含API的版本、Host、Authorization等信息。詳情請參看HTTP 請求結構

  • RequestBody:在 body 中的業務請求參數。詳情請參看HTTP 請求結構

請求RunInstances接口示例:

POST /?ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai HTTP/1.1
Authorization: ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=e521358f7776c97df52e6b2891a8bc73026794a071b50c3323388c4e0df64804
x-acs-action: RunInstances
host: ecs.cn-shanghai.aliyuncs.com
x-acs-date: 2023-10-26T09:01:01Z
x-acs-version: 2014-05-26
x-acs-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-acs-signature-nonce: d410180a5abf7fe235dd9b74aca91fc0
user-agent: AlibabaCloud (Mac OS X; x86_64) Java/1.8.0_352-b08 tea-util/0.2.6 TeaDSL/1
accept: application/json

請求編碼

請求及返回結果都使用UTF-8字符集進行編碼。

獲取API信息

訪問API文檔,選擇云產品。

  1. 單擊云服務器名稱下面的獲取元數據,在元數據中info.style查看云產品支持的OpenAPI風格(RPC或者ROA)。

    image

    image

    說明

    該位置獲取的元數據中包含了云產品的所有API信息,如果您想要查看單個API的元數據,請查看步驟2。

  2. 選擇將要調用的API,單擊右上角獲取元數據

    image

    在元數據中,定義了API支持的網絡協議、請求方式、參數及參數位置等信息。如下圖所示的RunInstances元數據中:

    • 支持的網絡協議有HTTP和HTTPS,建議使用HTTPS。

    • 支持的請求方式有GET和POST,兩者請求方式調用結果無任何差異,但GET請求只支持 32 KB 以內的請求包,所以推薦使用POST請求。

    • 支持的參數有RegionId、ImageId等,參數位置在query,表示參數是要拼接在請求URL后面,例如https://ecs.cn-beijing.aliyuncs.com/?ImageId=aliyun_2_1903_x64_20G_alibase_20231221.vhd&InstanceChargeType=PostPaid&InstanceType=ecs.e-c1m1.large&InternetChargeType=PayByTraffic&MinAmount=1&Password=test%401234&RegionId=cn-beijing&SecurityGroupId=sg-2zec0dm6qi66XXXXXXXX&SystemDisk.Category=cloud_essd&SystemDisk.Size=40&VSwitchId=vsw-2ze3aagwn397gXXXXXXXX。

    image

    說明

    元數據中其他支持的內容對簽名無影響,這里暫不詳細說明。更多元數據的信息,請參見元數據使用指南

簽名機制

為保證API的安全調用,在調用API時阿里云會對每個API請求通過簽名(Signature)進行身份驗證。無論使用HTTP還是HTTPS協議提交請求,都需要在請求中包含簽名信息。本文指導您如何進行簽名處理。

對于每一次HTTP或者HTTPS協議請求,阿里云會根據訪問中的簽名信息驗證訪問請求者身份。您在訪問時簽名信息時,請按照以下方法對請求進行簽名處理:

步驟一:構造規范化請求

使用AK/SK方式進行簽名與認證,首先需要規范請求內容,然后再進行簽名。客戶端與云服務API網關使用相同的請求規范,可以確保同一個HTTP請求的前后端得到相同的簽名結果,從而完成身份校驗。

構造規范化請求(CanonicalRequest)的偽代碼如下:

CanonicalRequest =
  HTTPRequestMethod + '\n' +    //http請求方法,全大寫
  CanonicalURI + '\n' +         //規范化URI
  CanonicalQueryString + '\n' + //規范化查詢字符串
  CanonicalHeaders + '\n' +     //規范化消息頭
  SignedHeaders + '\n' +        //已簽名消息頭
  HashedRequestPayload			
  • 請求方法(HTTPRequestMethod)

    即大寫的HTTP方法名,如GET、POST。

  • 規范化URI(CanonicalURI)

    即URL的資源路徑部分經過編碼得到,資源路徑部分指URL中host與查詢字符串之間的部分,包含host之后的/但不包含查詢字符串前的?。用戶發起請求時的URI應使用規范化URI,編碼方式使用UTF-8字符集按照RFC3986的規則對URI中的每一部分(即被/分割開的字符串)進行編碼:

    • 字符A~Z、a~z、0~9以及字符-_.~不編碼。

    • 其他字符編碼成%加字符對應ASCII碼的16進制。示例:半角雙引號(")對應%22

    • 空格( )編碼成%20,而不是加號(+)、星號(*)替換為%2A%7E替換為波浪號(~)。

      如果您使用的是Java標準庫中的java.net.URLEncoder,可以先用標準庫中encode編碼,隨后將編碼后的字符中加號(+)替換為%20、星號(*)替換為%2A%7E替換為波浪號(~),即可得到上述規則描述的編碼字符串。

    重要

    RPC風格API使用正斜杠(/)作為CanonicalURI,

    ROA風格API該參數為元數據文件中path的值,例如/api/v1/clusters。

  • 規范化查詢字符串(CanonicalQueryString)

    構造方法如下:

    1. 將查詢字符串中的參數按照參數名的字符代碼升序排列,具有重復名稱的參數應按值進行排序。

    2. 使用UTF-8字符集按照RFC3986的規則對每個參數的參數名和參數值分別進行URI編碼,具體規則與上一節中的CanonicalURI編碼規則相同。

    3. 使用等號(=)連接編碼后的請求參數名和參數值,對于沒有值的參數使用空字符串。

    4. 多個請求參數之間使用與號(&)連接。

    重要

    當請求的查詢字符串為空時,使用空字符串作為規范化查詢字符串。

  • 規范化請求頭(CanonicalHeaders

    一個非標準HTTP頭部信息。需要將請求中包含以x-acs-為前綴、hostcontent-type的參數信息,添加到規范化請求頭中,構造方法如下:

    1. 將所有需要簽名的參數的名稱轉換為小寫。

    2. 將所有參數按照參數名稱的字符順序以升序排列。

    3. 將參數的值除去首尾空格。對于有多個值的參數,將多個值分別除去首尾空格后按值升序排列,然后用逗號(,)連接。

    4. 將步驟2、3的結果以英文冒號(:)連接,并在尾部添加換行符,組成一個規范化消息頭(CanonicalHeaderEntry)。

    5. 如果沒有需要簽名的消息頭,使用空字符串作為規范化消息頭列表。

    重要

    除Authorization外的所有公共請求頭,只要符合要求的參數都必須被加入簽名。

    偽代碼如下:

    CanonicalHeaderEntry = Lowercase(HeaderName) + ':' + Trim(HeaderValue) + '\n'
    
    CanonicalHeaders = 
        CanonicalHeaderEntry0 + CanonicalHeaderEntry1 + ... + CanonicalHeaderEntryN
  • 已簽名消息頭列表(SignedHeaders

    用于說明此次請求包含了哪些消息頭參與簽名,與CanonicalHeaders中包含的消息頭是一一對應的,構造方法如下:

    • 將CanonicalHeaders中包含的請求頭的名稱轉為小寫。

    • 多個請求頭名稱(小寫)按首字母升序排列并以英文分號(;)分隔,例如 content-type;host;x-acs-date

    偽代碼如下:

    SignedHeaders = Lowercase(HeaderName0) + ';' + Lowercase(HeaderName1) + ... + Lowercase(HeaderNameN) 
    
  • HashedRequestPayload

    當請求體(body)為空時,RequestPayload固定為空字符串,否則RequestPayload的值為請求體(body)對應的JSON字符串。再使用哈希函數對RequestPayload進行轉換得到HashedRequestPayload,轉換規則用偽代碼可表示為 HashedRequestPayload = HexEncode(Hash(RequestPayload))

    • Hash表示消息摘要函數,目前支持SHA256算法,例如,當簽名協議使用ACS3-HMAC-SHA256時,應使用SHA256作為Hash函數。

    • HexEncode表示以小寫的十六進制的形式返回摘要的編碼函數(即Base16編碼)。

    表1:簽名協議與簽名算法、摘要函數的對應關系

    簽名協議(SignatureAlgorithm)

    處理RequestPayload以及CanonicalRequest時使用的摘要函數(Hash)

    計算簽名時實際使用的簽名算法

    (SignatureMethod)

    ACS3-HMAC-SHA256

    SHA256

    HMAC-SHA256

步驟二:構造待簽名字符串

按照以下偽代碼構造待簽名字符串(stringToSign):

StringToSign =
    SignatureAlgorithm + '\n' +
    HashedCanonicalRequest
  • SignatureAlgorithm

    簽名協議,目前支持ACS3-HMAC-SHA256,不再支持基于MD5或SHA1的算法。

  • HashedCanonicalRequest

    規范化請求摘要串,計算方法偽代碼如下:

    HashedCanonicalRequest = HexEncode(Hash(CanonicalRequest))
  1. 使用哈希函數(Hash)對步驟一中得到的規范化請求(CanonicalRequest)進行摘要處理,具體使用的Hash函數取決于簽名協議(SignatureAlgorithm),參見表1,例如,當簽名協議為ACS3-HMAC-SHA256時,應使用SHA256作為Hash函數。

  2. 將上一步得到的摘要結果以小寫的十六進制形式編碼。

步驟三:計算簽名

按照以下偽代碼計算簽名值(Signature)。

Signature = HexEncode(SignatureMethod(Secret, StringToSign))
  • StringToSign:步驟二中構造的待簽名字符串,UTF-8編碼。

  • SignatureMethod:簽名算法,具體使用的算法取決于簽名協議(SignatureAlgorithm),其對應關系如表1。

  • Secret:用戶的簽名密鑰,為二進制數據。

  • HexEncode:以小寫的十六進制的形式返回摘要的編碼函數(即Base16編碼)。

步驟四:將簽名添加到請求中

計算完簽名后,構造Authorization請求頭,格式為:Authorization:<SignatureAlgorithm> Credential=<AccessKeyId>,SignedHeaders=<SignedHeaders>,Signature=<Signature>示例如下:

Authorization:ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=content-type;host;x-acs-timestamp,Signature=6b595d672d79c15b18edb4ccfba6789a24a6f2b82c400e03162d9279b08555d7

接口簽名示例

說明
  1. 為了讓您更清晰地理解上述簽名機制,下面以主流編程語言為例,將簽名機制完整實現。示例代碼只是讓您更好地理解簽名機制,存在不通用性,阿里云OpenAPI提供多種編程語言和開發框架的SDK,使用這些SDK可以免去簽名過程,便于您快速構建與阿里云相關的應用程序,建議您使用SDK。

  2. 在簽名之前,一定要先查看API信息,獲取API的請求方式、請求參數以及參數位置等信息。

Java示例

運行Java示例,需要您在pom.xml中添加以下Maven依賴。

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>
<dependency>
     <groupId>com.google.code.gson</groupId>
     <artifactId>gson</artifactId>
     <version>2.9.0</version>
 </dependency>
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.http.entity.ContentType;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * signature demo
 */
public class SignatureDemo {

    /**
     * 日期格式化工具,用于將日期時間字符串格式化為"yyyy-MM-dd'T'HH:mm:ss'Z'"的格式。
     */
    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");

    private static class Request {
        // HTTP Method
        private final String httpMethod;
        // 請求路徑,當資源路徑為空時,使用正斜杠(/)作為CanonicalURI
        private final String canonicalUri;
        // endpoint
        private final String host;
        // API name
        private final String xAcsAction;
        // API version
        private final String xAcsVersion;
        // headers
        TreeMap<String, Object> headers = new TreeMap<>();
        // 調用API所需要的參數,參數位置在body。Json字符串
        String body;
        // 調用API所需要的參數,參數位置在query,參數按照參數名的字符代碼升序排列
        TreeMap<String, Object> queryParam = new TreeMap<>();

        public Request(String httpMethod, String canonicalUri, String host, String xAcsAction, String xAcsVersion) {
            this.httpMethod = httpMethod;
            this.canonicalUri = canonicalUri;
            this.host = host;
            this.xAcsAction = xAcsAction;
            this.xAcsVersion = xAcsVersion;
            initBuilder();
        }

        // init headers
        private void initBuilder() {
            headers.put("host", host);
            headers.put("x-acs-action", xAcsAction);
            headers.put("x-acs-version", xAcsVersion);
            SDF.setTimeZone(new SimpleTimeZone(0, "GMT")); // 設置日期格式化時區為GMT
            headers.put("x-acs-date", SDF.format(new Date()));
            headers.put("x-acs-signature-nonce", UUID.randomUUID().toString());
        }
    }

    /**
     * 這里通過環境變量獲取Access Key ID和Access Key Secret,
     */
    private final static String ACCESS_KEY_ID = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
    private final static String ACCESS_KEY_SECRET = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");


    /**
     * 簽名協議
     */
    private static final String ALGORITHM = "ACS3-HMAC-SHA256";

    /**
     * 主函數示例,演示如何使用阿里云的SDK進行API請求。
     * 通過給定的參數構建請求,并進行簽名認證。
     */
    public static void main(String[] args) {
        // RPC接口請求
        String httpMethod = "POST"; // 請求方式
        String canonicalUri = "/"; 
        String host = "ecs.cn-beijing.aliyuncs.com";  // endpoint
        String xAcsAction = "DescribeInstances";  // API名稱
        String xAcsVersion = "2014-05-26"; // API版本號
        Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);

        // 調用API所需要的參數,參數按照參數名的字符代碼升序排列,具有重復名稱的參數應按值進行排序。
        request.queryParam.put("RegionId", "cn-beijing");
        request.queryParam.put("VpcId", "vpc-2zeo42r27y4opXXXXXXXX");


/*         // ROA接口POST請求
        String httpMethod = "POST";
        String canonicalUri = "/clusters"; 
        String host = "cs.cn-beijing.aliyuncs.com"; // endpoint
        String xAcsAction= "CreateCluster"; // API名稱
        String xAcsVersion=  "2015-12-15"; // API版本號
        Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);

        // 請求body,通過Gson將body轉成JSON字符串
        TreeMap<String, Object> body = new TreeMap<>();
        body.put("name", "testDemo");
        body.put("region_id", "cn-beijing");
        body.put("cluster_type", "Kubernetes");
        body.put("vpcid", "vpc-2zeo42r27y4opXXXXXXXX");
        body.put("service_cidr", "172.16.1.0/20");
        body.put("security_group_id", "sg-2zec0dm6qi66XXXXXXXX");
        Gson gson = (new GsonBuilder()).disableHtmlEscaping().create();
        request.body = gson.toJson(body);
        request.headers.put("content-type", "application/json; charset=utf-8");*/

/*        // ROA接口GET請求
        String httpMethod = "GET";
        // canonicalUri如果存在path參數,需要對path參數encode,percentCode({path參數})
        String canonicalUri = "/clusters/" + percentCode("cb7cd6b9bde934f6193801878XXXXXXXX") + "/resources";
        String host = "cs.cn-beijing.aliyuncs.com"; // endpoint
        String xAcsAction = "DescribeClusterResources"; // API名稱
        String xAcsVersion = "2015-12-15"; // API版本號
        Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);

        request.queryParam.put("with_addon_resources", true);*/

        // 簽名過程
        getAuthorization(request);
        // 調用API
        callApi(request);
    }

    private static void callApi(Request request) {
        try {
            // 通過HttpClient發送請求
            String url = "https://" + request.host + request.canonicalUri;
            URIBuilder uriBuilder = new URIBuilder(url);
            // 添加請求參數
            for (Map.Entry<String, Object> entry : request.queryParam.entrySet()) {
                uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue()));
            }
            HttpUriRequest httpRequest;
            switch (request.httpMethod) {
                case "GET":
                    httpRequest = new HttpGet(uriBuilder.build());
                    break;
                case "POST":
                    HttpPost httpPost = new HttpPost(uriBuilder.build());
                    if (request.body != null) {
                       httpPost.setEntity(new StringEntity(request.body, ContentType.APPLICATION_JSON));
                    }
                    httpRequest = httpPost;
                    break;
                case "DELETE":
                    httpRequest = new HttpDelete(uriBuilder.build());
                    break;
                case "PUT":
                    HttpPut httpPut = new HttpPut(uriBuilder.build());
                    if (request.body != null) {
                        StringEntity putEntity = new StringEntity(request.body);
                        httpPut.setEntity(putEntity);
                    }
                    httpRequest = httpPut;
                    break;
                default:
                    System.out.println("Unsupported HTTP method: " + request.body);
                    throw new IllegalArgumentException("Unsupported HTTP method");
            }

            // 添加http請求頭
            for (Map.Entry<String, Object> entry : request.headers.entrySet()) {
                httpRequest.addHeader(entry.getKey(), String.valueOf(entry.getValue()));
            }
            // 發送請求
            try (CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(httpRequest)) {
                String result = EntityUtils.toString(response.getEntity(), "UTF-8");
                System.out.println(result);
            } catch (IOException e) {
                // 異常處理
                System.out.println("Failed to send request");
                e.printStackTrace();
            }
        } catch (URISyntaxException e) {
            // 異常處理
            System.out.println("Invalid URI syntax");
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            // 異常處理
            System.out.println("UnsupportedEncodingException");
            e.printStackTrace();
        }
    }

    /**
     * 該方法用于根據傳入的HTTP請求方法、規范化的URI、查詢參數等,計算并生成授權信息。
     */
    private static void getAuthorization(Request request) {
        try {
            // 步驟 1:拼接規范請求串
            // 請求參數,當請求的查詢字符串為空時,使用空字符串作為規范化查詢字符串
            StringBuilder canonicalQueryString = new StringBuilder();
            request.queryParam.entrySet().stream().map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue()))).forEachOrdered(queryPart -> {
                // 如果canonicalQueryString已經不是空的,則在查詢參數前添加"&"
                if (canonicalQueryString.length() > 0) {
                    canonicalQueryString.append("&");
                }
                canonicalQueryString.append(queryPart);
            });

            // 請求體,當請求正文為空時,比如GET請求,RequestPayload固定為空字符串
            String requestPayload = "";
            if (request.body != null) {
                requestPayload = request.body;
            }

            // 計算請求體的哈希值
            String hashedRequestPayload = sha256Hex(requestPayload);
            request.headers.put("x-acs-content-sha256", hashedRequestPayload);
            // 構造請求頭,多個規范化消息頭,按照消息頭名稱(小寫)的字符代碼順序以升序排列后拼接在一起
            StringBuilder canonicalHeaders = new StringBuilder();
            // 已簽名消息頭列表,多個請求頭名稱(小寫)按首字母升序排列并以英文分號(;)分隔
            StringBuilder signedHeadersSb = new StringBuilder();
            request.headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") || entry.getKey().equalsIgnoreCase("host") || entry.getKey().equalsIgnoreCase("content-type")).sorted(Map.Entry.comparingByKey()).forEach(entry -> {
                String lowerKey = entry.getKey().toLowerCase();
                String value = String.valueOf(entry.getValue()).trim();
                canonicalHeaders.append(lowerKey).append(":").append(value).append("\n");
                signedHeadersSb.append(lowerKey).append(";");
            });
            String signedHeaders = signedHeadersSb.substring(0, signedHeadersSb.length() - 1);
            String canonicalRequest = request.httpMethod + "\n" + request.canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
            System.out.println("canonicalRequest=========>\n" + canonicalRequest);

            // 步驟 2:拼接待簽名字符串
            String hashedCanonicalRequest = sha256Hex(canonicalRequest); // 計算規范化請求的哈希值
            String stringToSign = ALGORITHM + "\n" + hashedCanonicalRequest;
            System.out.println("stringToSign=========>\n" + stringToSign);

            // 步驟 3:計算簽名
            String signature = DatatypeConverter.printHexBinary(hmac256(ACCESS_KEY_SECRET.getBytes(StandardCharsets.UTF_8), stringToSign)).toLowerCase();
            System.out.println("signature=========>" + signature);

            // 步驟 4:拼接 Authorization
            String authorization = ALGORITHM + " " + "Credential=" + ACCESS_KEY_ID + ",SignedHeaders=" + signedHeaders + ",Signature=" + signature;
            System.out.println("authorization=========>" + authorization);
            request.headers.put("Authorization", authorization);
        } catch (Exception e) {
            // 異常處理
            System.out.println("Failed to get authorization");
            e.printStackTrace();
        }
    }

    /**
     * 使用HmacSHA256算法生成消息認證碼(MAC)。
     *
     * @param key 密鑰,用于生成MAC的密鑰,必須保密。
     * @param str 需要進行MAC認證的消息。
     * @return 返回使用HmacSHA256算法計算出的消息認證碼。
     * @throws Exception 如果初始化MAC或計算MAC過程中遇到錯誤,則拋出異常。
     */
    public static byte[] hmac256(byte[] key, String str) throws Exception {
        // 實例化HmacSHA256消息認證碼生成器
        Mac mac = Mac.getInstance("HmacSHA256");
        // 創建密鑰規范,用于初始化MAC生成器
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
        // 初始化MAC生成器
        mac.init(secretKeySpec);
        // 計算消息認證碼并返回
        return mac.doFinal(str.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 使用SHA-256算法計算字符串的哈希值并以十六進制字符串形式返回。
     *
     * @param str 需要進行SHA-256哈希計算的字符串。
     * @return 計算結果為小寫十六進制字符串。
     * @throws Exception 如果在獲取SHA-256消息摘要實例時發生錯誤。
     */
    public static String sha256Hex(String str) throws Exception {
        // 獲取SHA-256消息摘要實例
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        // 計算字符串s的SHA-256哈希值
        byte[] d = md.digest(str.getBytes(StandardCharsets.UTF_8));
        // 將哈希值轉換為小寫十六進制字符串并返回
        return DatatypeConverter.printHexBinary(d).toLowerCase();
    }

    /**
     * 對指定的字符串進行URL編碼。
     * 使用UTF-8編碼字符集對字符串進行編碼,并對特定的字符進行替換,以符合URL編碼規范。
     *
     * @param str 需要進行URL編碼的字符串。
     * @return 編碼后的字符串。其中,加號"+"被替換為"%20",星號"*"被替換為"%2A",波浪號"%7E"被替換為"~"。
     */
    public static String percentCode(String str) {
        if (str == null) {
            throw new IllegalArgumentException("輸入字符串不可為null");
        }
        try {
            return URLEncoder.encode(str, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("UTF-8編碼不被支持", e);
        }
    }
}

Python示例

需要您手動安裝pytz和requests,請根據您所使用的Python版本在終端(Terminal)執行以下命令。

Python3

pip3 install pytz
pip3 install requests

Python2

pip install pytz
pip install requests
import hashlib
import hmac
import os
import uuid
from collections import OrderedDict
from urllib.parse import urlencode, quote_plus

import pytz
import requests
from datetime import datetime


class Request:
    def __init__(self, http_method, canonical_uri, host, x_acs_action, x_acs_version):
        self.http_method = http_method
        self.canonical_uri = canonical_uri
        self.host = host
        self.x_acs_action = x_acs_action
        self.x_acs_version = x_acs_version
        self.headers = self._init_headers()
        self.query_param = OrderedDict()
        self.body = None

    def _init_headers(self):
        headers = OrderedDict()
        headers['host'] = self.host
        headers['x-acs-action'] = self.x_acs_action
        headers['x-acs-version'] = self.x_acs_version
        current_time = datetime.now(pytz.timezone('Etc/GMT'))
        headers['x-acs-date'] = current_time.strftime('%Y-%m-%dT%H:%M:%SZ')
        headers['x-acs-signature-nonce'] = str(uuid.uuid4())
        return headers

    def sorted_query_params(self):
        # 對查詢參數按名稱排序并返回編碼后的字符串
        sorted_query_params = sorted(self.query_param.items(), key=lambda item: item[0])
        self.query_param = {k: v for k, v in sorted_query_params}

    def sorted_headers(self):
        # 對請求頭按名稱排序并返回編碼后的字符串
        sorted_headers = sorted(self.headers.items(), key=lambda item: item[0])
        self.headers = {k: v for k, v in sorted_headers}


# 環境變量中獲取Access Key ID和Access Key Secret
ACCESS_KEY_ID = os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID')
ACCESS_KEY_SECRET = os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET')

ALGORITHM = 'ACS3-HMAC-SHA256'


def get_authorization(request):
    try:
        # Step 1: Construct Canonical Query String and Payload Hash
        canonical_query_string = '&'.join(
            f'{percent_code(quote_plus(k))}={percent_code(quote_plus(str(v)))}' for k, v in request.query_param.items())
        hashed_request_payload = sha256_hex(request.body or '')
        request.headers['x-acs-content-sha256'] = hashed_request_payload
        request.sorted_headers()

        # Construct Canonical Headers and Signed Headers
        filtered_headers = OrderedDict()
        for k, v in request.headers.items():
            if k.lower().startswith('x-acs-') or k.lower() in ['host', 'content-type']:
                filtered_headers[k.lower()] = v

        canonical_headers = '\n'.join(f'{k}:{v}' for k, v in filtered_headers.items())
        signed_headers = ';'.join(k for k in filtered_headers.keys())

        canonical_request = f'{request.http_method}\n{request.canonical_uri}\n{canonical_query_string}\n{canonical_headers}\n\n{signed_headers}\n{hashed_request_payload}'
        print(canonical_request)

        # Step 2: Construct String to Sign
        hashed_canonical_request = sha256_hex(canonical_request)
        string_to_sign = f'{ALGORITHM}\n{hashed_canonical_request}'
        print(string_to_sign)

        # Step 3: Compute Signature
        signature = hmac256(ACCESS_KEY_SECRET.encode('utf-8'), string_to_sign).hex().lower()
        print(signature)

        # Step 4: Construct Authorization Header
        authorization = f'{ALGORITHM} Credential={ACCESS_KEY_ID},SignedHeaders={signed_headers},Signature={signature}'
        print(authorization)
        request.headers['Authorization'] = authorization
    except Exception as e:
        print("Failed to get authorization")
        print(e)


def hmac256(key, msg):
    return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()


def sha256_hex(s):
    return hashlib.sha256(s.encode('utf-8')).hexdigest()


def call_api(request):
    url = f'https://{request.host}{request.canonical_uri}'
    if request.query_param:
        url += '?' + urlencode(request.query_param, doseq=True, safe='*')
    headers = {k: v for k, v in request.headers.items()}
    if request.body:
        data = request.body
    else:
        data = None

    try:
        response = requests.request(method=request.http_method, url=url, headers=headers, data=data)
        response.raise_for_status()
        print(response.text)
    except requests.RequestException as e:
        print("Failed to send request")
        print(e)


def percent_code(encoded_str):
    return encoded_str.replace('+', '%20').replace('*', '%2A').replace('%7E', '~')


if __name__ == "__main__":
    # RPC接口請求
    http_method = "POST"
    canonical_uri = "/"
    host = "ecs.cn-beijing.aliyuncs.com"
    x_acs_action = "DescribeInstances"
    x_acs_version = "2014-05-26"
    request = Request(http_method, canonical_uri, host, x_acs_action, x_acs_version)
    request.query_param['RegionId'] = 'cn-beijing'
    request.query_param['VpcId'] = 'vpc-2zeo42r27y4opXXXXXXXX'

    # ROA接口POST請求
    # http_method = "POST"
    # canonical_uri = "/clusters"
    # host = "cs.cn-beijing.aliyuncs.com"
    # x_acs_action = "CreateCluster"
    # x_acs_version = "2015-12-15"
    # request = Request(http_method, canonical_uri, host, x_acs_action, x_acs_version)
    # body = OrderedDict()
    # body["name"] = "testDemo"
    # body["region_id"] = "cn-beijing"
    # body["cluster_type"] = "Kubernetes"
    # body["vpcid"] = "vpc-2zeo42r27y4opXXXXXXXX"
    # body["service_cidr"] = "172.16.1.0/20"
    # body["security_group_id"] = "sg-2zec0dm6qi66XXXXXXXX"
    #
    # request.body = json.dumps(body, separators=(',', ':'))
    # request.headers["content-type"] = "application/json; charset=utf-8"

    # ROA接口GET請求
    # http_method = "GET"
    # # canonicalUri如果存在path參數,需要對path參數encode,percent_code({path參數})
    # cluster_id_encode = percent_code("cb7cd6b9bde934f6193801878XXXXXXXX")
    # canonical_uri = f"/clusters/{cluster_id_encode}/resources"
    # host = "cs.cn-beijing.aliyuncs.com"
    # x_acs_action = "DescribeClusterResources"
    # x_acs_version = "2015-12-15"
    # request = Request(http_method, canonical_uri, host, x_acs_action, x_acs_version)
    # request.query_param['with_addon_resources'] = True

    request.sorted_query_params()
    get_authorization(request)
    call_api(request)

Go示例

需要您在終端(Terminal)執行以下命令:

go get github.com/google/uuid
go get golang.org/x/exp/maps
package main

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"io"
	"os"
	"sort"

	"golang.org/x/exp/maps"

	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/google/uuid"
)

type Request struct {
	httpMethod   string
	canonicalUri string
	host         string
	xAcsAction   string
	xAcsVersion  string
	headers      map[string]string
	body         string
	queryParam   map[string]string
}

func NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion string) *Request {
	req := &Request{
		httpMethod:   httpMethod,
		canonicalUri: canonicalUri,
		host:         host,
		xAcsAction:   xAcsAction,
		xAcsVersion:  xAcsVersion,
		headers:      make(map[string]string),
		queryParam:   make(map[string]string),
	}
	req.headers["host"] = host
	req.headers["x-acs-action"] = xAcsAction
	req.headers["x-acs-version"] = xAcsVersion
	req.headers["x-acs-date"] = time.Now().UTC().Format(time.RFC3339)
	req.headers["x-acs-signature-nonce"] = uuid.New().String()
	return req
}

var (
	AccessKeyId     = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
	AccessKeySecret = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
	ALGORITHM       = "ACS3-HMAC-SHA256"
)

func main() {
	// RPC接口請求
	httpMethod := "POST"
	canonicalUri := "/"
	host := "ecs.cn-beijing.aliyuncs.com"
	xAcsAction := "DescribeInstances"
	xAcsVersion := "2014-05-26"
	req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
	req.queryParam["RegionId"] = "cn-beijing"
	req.queryParam["VpcId"] = "vpc-2zeo42r27y4opXXXXXXXX"

	/*    // ROA接口POST請求
	httpMethod := "POST"
	canonicalUri := "/clusters"
	host := "cs.cn-beijing.aliyuncs.com"
	xAcsAction := "CreateCluster"
	xAcsVersion := "2015-12-15"
	req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
	body := make(map[string]string)
	body["name"] = "testDemo"
	body["region_id"] = "cn-beijing"
	body["cluster_type"] = "Kubernetes"
	body["vpcid"] = "vpc-2zeo42r27y4opXXXXXXXX"
	body["service_cidr"] = "172.16.1.0/20"
	body["security_group_id"] = "sg-2zec0dm6qi66XXXXXXXX"
	jsonBytes, err := json.Marshal(body)
	if err != nil {
	fmt.Println("Error marshaling to JSON:", err)
	return
	}
	req.body = string(jsonBytes)
	req.headers["content-type"] = "application/json; charset=utf-8"*/

	/*    // ROA接口GET請求
	httpMethod := "GET"
	            // canonicalUri如果存在path參數,需要對path參數encode,percentCode({path參數})
	canonicalUri := "/clusters/" + percentCode("cb7cd6b9bde934f6193801878XXXXXXXX") + "/resources"
	host := "cs.cn-beijing.aliyuncs.com"
	xAcsAction := "DescribeClusterResources"
	xAcsVersion := "2015-12-15"
	req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
	req.queryParam["with_addon_resources"] = "true"*/

	// 簽名過程
	getAuthorization(req)
	// 調用API
	err := callAPI(req)
	if err != nil {
		println(err.Error())
	}
}

func callAPI(req *Request) error {
	urlStr := "https://" + req.host + req.canonicalUri
	q := url.Values{}
	keys := maps.Keys(req.queryParam)
	sort.Strings(keys)
	for _, k := range keys {
		v := req.queryParam[k]
		q.Set(k, v)
	}
	urlStr += "?" + q.Encode()
	fmt.Println(urlStr)

	httpReq, err := http.NewRequest(req.httpMethod, urlStr, strings.NewReader(req.body))
	if err != nil {
		return err
	}

	for key, value := range req.headers {
		httpReq.Header.Set(key, value)
	}

	client := &http.Client{}
	resp, err := client.Do(httpReq)
	if err != nil {
		return err
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
			return
		}
	}(resp.Body)
	var respBuffer bytes.Buffer
	_, err = io.Copy(&respBuffer, resp.Body)
	if err != nil {
		return err
	}
	respBytes := respBuffer.Bytes()
	fmt.Println(string(respBytes))
	return nil
}

func getAuthorization(req *Request) {
	canonicalQueryString := ""
	keys := maps.Keys(req.queryParam)
	sort.Strings(keys)
	for _, k := range keys {
		v := req.queryParam[k]
		canonicalQueryString += percentCode(url.QueryEscape(k)) + "=" + percentCode(url.QueryEscape(v)) + "&"
	}
	canonicalQueryString = strings.TrimSuffix(canonicalQueryString, "&")
	fmt.Printf("canonicalQueryString========>%s\n", canonicalQueryString)

	hashedRequestPayload := sha256Hex(req.body)
	req.headers["x-acs-content-sha256"] = hashedRequestPayload

	canonicalHeaders := ""
	signedHeaders := ""
	HeadersKeys := maps.Keys(req.headers)
	sort.Strings(HeadersKeys)
	for _, k := range HeadersKeys {
		lowerKey := strings.ToLower(k)
		if lowerKey == "host" || strings.HasPrefix(lowerKey, "x-acs-") || lowerKey == "content-type" {
			canonicalHeaders += lowerKey + ":" + req.headers[k] + "\n"
			signedHeaders += lowerKey + ";"
		}
	}
	signedHeaders = strings.TrimSuffix(signedHeaders, ";")

	canonicalRequest := req.httpMethod + "\n" + req.canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload
	fmt.Printf("canonicalRequest========>\n%s\n", canonicalRequest)

	hashedCanonicalRequest := sha256Hex(canonicalRequest)
	stringToSign := ALGORITHM + "\n" + hashedCanonicalRequest
	fmt.Printf("stringToSign========>\n%s\n", stringToSign)

	byteData, err := hmac256([]byte(AccessKeySecret), stringToSign)
	if err != nil {
		fmt.Println(err)
	}
	signature := strings.ToLower(hex.EncodeToString(byteData))

	authorization := ALGORITHM + " Credential=" + AccessKeyId + ",SignedHeaders=" + signedHeaders + ",Signature=" + signature
	fmt.Printf("authorization========>%s\n", authorization)
	req.headers["Authorization"] = authorization
}

func hmac256(key []byte, toSignString string) ([]byte, error) {
	// 實例化HMAC-SHA256哈希
	h := hmac.New(sha256.New, key)
	// 寫入待簽名的字符串
	_, err := h.Write([]byte(toSignString))
	if err != nil {
		return nil, err
	}
	// 計算簽名并返回
	return h.Sum(nil), nil
}

func sha256Hex(str string) string {
	// 實例化SHA-256哈希函數
	hash := sha256.New()
	// 將字符串寫入哈希函數
	_, _ = hash.Write([]byte(str))
	// 計算SHA-256哈希值并轉換為小寫的十六進制字符串
	hexString := hex.EncodeToString(hash.Sum(nil))

	return hexString
}

func percentCode(str string) string {
	// 替換特定的編碼字符
	str = strings.ReplaceAll(str, "+", "%20")
	str = strings.ReplaceAll(str, "*", "%2A")
	str = strings.ReplaceAll(str, "%7E", "~")
	return str
}

Node.js示例

本示例所用語言是javaScript。

const crypto = require('crypto');

class Request {
    constructor(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion) {
        this.httpMethod = httpMethod;
        this.canonicalUri = canonicalUri || '/';
        this.host = host;
        this.xAcsAction = xAcsAction;
        this.xAcsVersion = xAcsVersion;
        this.headers = {};
        this.body = null;
        this.queryParam = {};
        this.initBuilder();
    }

    initBuilder() {
        const date = new Date();
        this.headers = {
            'host': this.host,
            'x-acs-action': this.xAcsAction,
            'x-acs-version': this.xAcsVersion,
            'x-acs-date': date.toISOString().replace(/\..+/, 'Z'),
            'x-acs-signature-nonce': crypto.randomBytes(16).toString('hex')
        }
    }
}

const ALGORITHM = 'ACS3-HMAC-SHA256';
const accessKeyId = process.env.ALIBABA_CLOUD_ACCESS_KEY_ID;
const accessKeySecret = process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET;

if (!accessKeyId || !accessKeySecret) {
    console.error('ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables must be set.');
    process.exit(1);
}

function getAuthorization(signRequest) {
    try {
        // 步驟 1:拼接規范請求串
        const canonicalQueryString = Object.entries(signRequest.queryParam)
            .sort((a, b) => a[0].localeCompare(b[0]))
            .map(([key, value]) => `${percentCode(key)}=${percentCode(value)}`)
            .join('&');

        // 請求體,當請求正文為空時,比如GET請求,RequestPayload固定為空字符串
        const requestPayload = signRequest.body || '';
        const hashedRequestPayload = sha256Hex(requestPayload);
        signRequest.headers['x-acs-content-sha256'] = hashedRequestPayload;

        // 將所有key都轉換為小寫
        signRequest.headers = Object.fromEntries(
            Object.entries(signRequest.headers).map(([key, value]) => [key.toLowerCase(), value])
        );

        const sortedKeys = Object.keys(signRequest.headers)
            .filter(key => key.startsWith('x-acs-') || key === 'host' || key === 'content-type')
            .sort();
        // 已簽名消息頭列表,多個請求頭名稱(小寫)按首字母升序排列并以英文分號(;)分隔
        const signedHeaders = sortedKeys.join(";")
        // 構造請求頭,多個規范化消息頭,按照消息頭名稱(小寫)的字符代碼順序以升序排列后拼接在一起
        const canonicalHeaders = sortedKeys.reduce((result, key) => {
            const value = signRequest.headers[key];
            // 根據需要格式化結果字符串
            return `${result}${key}:${value}\n`;
        }, '');

        const canonicalRequest = `${signRequest.httpMethod}\n${signRequest.canonicalUri}\n${canonicalQueryString}\n${canonicalHeaders}\n${signedHeaders}\n${hashedRequestPayload}`;
        console.log('canonicalRequest=========>\n', canonicalRequest);

        // 步驟 2:拼接待簽名字符串
        const hashedCanonicalRequest = sha256Hex(canonicalRequest);
        const stringToSign = ALGORITHM + '\n' + hashedCanonicalRequest;
        console.log('stringToSign=========>', stringToSign);

        // 步驟 3:計算簽名
        const signature = hmac256(accessKeySecret, stringToSign);
        console.log('signature=========>', signature);

        // 步驟 4:拼接 Authorization
        const authorization = `${ALGORITHM} Credential=${accessKeyId},SignedHeaders=${signedHeaders},Signature=${signature}`;
        console.log('authorization=========>', authorization);
        signRequest.headers['Authorization'] = authorization;
    } catch (error) {
        console.error('Failed to get authorization');
        console.error(error);
    }
}

async function callApi(signRequest) {
    try {
        let url = `https://${signRequest.host}${signRequest.canonicalUri}`;
        // 添加請求參數
        if (signRequest.queryParam) {
            let query = new URLSearchParams();
            for (let [key, value] of Object.entries(signRequest.queryParam)) {
                query.append(key, String(value));
            }
            url += '?' + query.toString();
        }
        // 配置請求選項
        let options = {
            method: signRequest.httpMethod.toUpperCase(),
            headers: {}
        };
        // 添加HTTP請求頭
        if (signRequest.headers) {
            for (let [key, value] of Object.entries(signRequest.headers)) {
                options.headers[key] = String(value);
            }
        }
        // 處理請求體
        if (signRequest.body && ['POST', 'PUT'].includes(signRequest.httpMethod.toUpperCase())) {
            options.body = signRequest.body;
        }
        try {
            return (await fetch(url, options)).text();
        } catch (error) {
            // handler error
            console.error('Failed to send request:', error);
        }
    } catch (error) {
        if (error instanceof TypeError) {
            // handler error
            console.error('Invalid URI syntax');
        } else {
            // handler error
            console.error('An unexpected error occurred:', error);
        }
    }
}

function percentCode(str) {
    return encodeURIComponent(str)
        .replace(/\+/g, '%20')
        .replace(/\*/g, '%2A')
        .replace(/~/g, '%7E');
}

function hmac256(key, data) {
    const hmac = crypto.createHmac('sha256', Buffer.from(key, 'binary'));
    hmac.update(data, 'utf8');
    return hmac.digest('hex').toLowerCase();
}

function sha256Hex(str) {
    if (str === null || str === undefined) {
        throw new Error('輸入字符串不可為null或undefined');
    }
    const hash = crypto.createHash('sha256');
    const digest = hash.update(str, 'utf8').digest('hex');
    return digest.toLowerCase();
}

// 示例一:RPC接口請求
const httpMethod = 'GET';
const canonicalUri = '/';
const host = 'ecs.cn-beijing.aliyuncs.com';
const xAcsAction = 'DescribeInstances';
const xAcsVersion = '2014-05-26';
const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
signRequest.queryParam = {
    RegionId: 'cn-beijing',
    VpcId: 'vpc-2zeo42r27y4opXXXXXXXX',
}

// 示例二:ROA接口POST請求
// const httpMethod = 'POST';
// const canonicalUri = '/clusters';
// const host = 'cs.cn-beijing.aliyuncs.com';
// const xAcsAction = 'CreateCluster';
// const xAcsVersion = '2015-12-15';
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// const body = {
//     name: 'testDemo',
//     region_id: 'cn-beijing',
//     cluster_type: 'ExternalKubernetes',
//     vpcid: 'vpc-2zeo42r27y4opXXXXXXXX',
//     service_cidr: '172.16.3.0/20',
//     security_group_id: 'sg-2zeh5ta2iklXXXXXXXX',
//     vswitch_ids: [
//         'vsw-2ze3aagwn397gXXXXXXXX'
//       ],
// }
// signRequest.body = JSON.stringify(body)
// // 根據需要設置正確的Content-Type,這里假設是JSON
// signRequest.headers['content-type'] = 'application/json';

// 示例三:ROA接口GET請求
// const httpMethod = 'GET';
// // canonicalUri如果存在path參數,需要對path參數encode,percentCode({path參數})
// const canonicalUri = '/clusters/" + percentCode("cb7cd6b9bde934f6193801878XXXXXXXX") + "/resources';
// const host = 'cs.cn-beijing.aliyuncs.com';
// const xAcsAction = 'DescribeClusterResources';
// const xAcsVersion = '2015-12-15';
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// signRequest.queryParam = {
//     with_addon_resources: true,
// }

getAuthorization(signRequest);
callApi(signRequest).then(r => {
    console.log(r);
});

PHP示例

<?php

class SignatureDemo
{
    private $ALGORITHM;
    private $AccessKeyId;
    private $AccessKeySecret;

    public function __construct()
    {
        date_default_timezone_set('UTC'); // 設置時區為GMT
        $this->AccessKeyId = getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'); // 從環境變量中獲取RAM用戶Access Key ID
        $this->AccessKeySecret = getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'); // 從環境變量中獲取RAM用戶Access Key Secret
        $this->ALGORITHM = 'ACS3-HMAC-SHA256'; // 設置加密算法
    }

    public function main()
    {
        // RPC接口請求
        $request = $this->createRequest('POST', '/', 'ecs.cn-beijing.aliyuncs.com', 'DescribeRegions', '2014-05-26');
        $request['queryParam']=  ['RegionId' => 'cn-beijing'];

        // ROA接口POST請求
        // $request = $this->createRequest('POST', '/clusters', 'cs.cn-beijing.aliyuncs.com', 'CreateCluster', '2015-12-15');
        // $this->addRequestBody($request, [
        //     'name' => 'testDemo',
        //     'region_id' => 'cn-beijing',
        //     'cluster_type' => 'ExternalKubernetes',
        //     'vpcid' => 'vpc-2zeo42r27y4opXXXXXXXX',
        //     'service_cidr' => '172.16.5.0/20',
        //     'security_group_id' => 'sg-2zeh5ta2ikljXXXXXXXX',
        //     "vswitch_ids" => [
        //         "vsw-2zeuntqtklsk0XXXXXXXX"
        //     ],
        // ]);

        // ROA接口GET請求
        // canonicalUri如果存在path參數,需要對path參數encode,rawurlencode({path參數})
        // $cluster_id = 'cb7cd6b9bde934f6193801878XXXXXXXX';
        // $canonicalUri = sprintf("/clusters/%s/resources", rawurlencode($cluster_id));
        // $request = $this->createRequest('GET', $canonicalUri, 'cs.cn-beijing.aliyuncs.com', 'DescribeClusterResources', '2015-12-15');
        // $request['queryParam'] = [
        //     'with_addon_resources' => true,
        // ];

        $this->getAuthorization($request);
        $this->callApi($request);
    }

    private function createRequest($httpMethod, $canonicalUri, $host, $xAcsAction, $xAcsVersion)
    {
        $headers = [
            'host' => $host,
            'x-acs-action' => $xAcsAction,
            'x-acs-version' => $xAcsVersion,
            'x-acs-date' => gmdate('Y-m-d\TH:i:s\Z'),
            'x-acs-signature-nonce' => bin2hex(random_bytes(16)),
        ];
        return [
            'httpMethod' => $httpMethod,
            'canonicalUri' => $canonicalUri,
            'host' => $host,
            'headers' => $headers,
            'queryParam' => [],
            'body' => null,
        ];
    }

    private function addRequestBody(&$request, $bodyData)
    {
        $request['body'] = json_encode($bodyData, JSON_UNESCAPED_UNICODE);
        $request['headers']['content-type'] = 'application/json; charset=utf-8';
    }

    private function getAuthorization(&$request)
    {
        $canonicalQueryString = $this->buildCanonicalQueryString($request['queryParam']);
        $hashedRequestPayload = hash('sha256', $request['body'] ?? '');
        $request['headers']['x-acs-content-sha256'] = $hashedRequestPayload;

        $canonicalHeaders = $this->buildCanonicalHeaders($request['headers']);
        $signedHeaders = $this->buildSignedHeaders($request['headers']);

        $canonicalRequest = implode("\n", [
            $request['httpMethod'],
            $request['canonicalUri'],
            $canonicalQueryString,
            $canonicalHeaders,
            $signedHeaders,
            $hashedRequestPayload,
        ]);

        $hashedCanonicalRequest = hash('sha256', $canonicalRequest);
        $stringToSign = $this->ALGORITHM . "\n" . $hashedCanonicalRequest;

        $signature = strtolower(bin2hex(hash_hmac('sha256', $stringToSign, $this->AccessKeySecret, true)));
        $authorization = $this->ALGORITHM . " Credential=" . $this->AccessKeyId . ",SignedHeaders=" . $signedHeaders . ",Signature=" . $signature;

        $request['headers']['Authorization'] = $authorization;
    }

    private function callApi($request)
    {
        try {
            // 通過cURL發送請求
            $url = "https://" . $request['host'] . $request['canonicalUri'];

            // 初始化cURL會話
            $ch = curl_init();

            // 根據請求類型設置cURL選項
            switch ($request['httpMethod']) {
                case "GET":
                    break;
                case "POST":
                    curl_setopt($ch, CURLOPT_POST, true);
                    curl_setopt($ch, CURLOPT_POSTFIELDS, $request['body']);
                    break;
                case "DELETE":
                    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
                    break;
                case "PUT":
                    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
                    curl_setopt($ch, CURLOPT_POSTFIELDS, $request['body']);
                    break;
                default:
                    echo "Unsupported HTTP method: " . $request['body'];
                    throw new Exception("Unsupported HTTP method");
            }

            // 添加請求參數到URL
            if (!empty($request['queryParam'])) {
                $url .= '?' . http_build_query($request['queryParam']);
            }

            // 設置cURL選項
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 禁用SSL證書驗證,請注意,這會降低安全性,不應在生產環境中使用(不推薦!!!)
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 返回而不是輸出內容
            curl_setopt($ch, CURLOPT_HTTPHEADER, $this->convertHeadersToArray($request['headers'])); // 添加請求頭

            // 發送請求
            $result = curl_exec($ch);

            // 檢查是否有錯誤發生
            if (curl_errno($ch)) {
                echo "Failed to send request: " . curl_error($ch);
            } else {
                echo $result;
            }

            // 關閉cURL會話
            curl_close($ch);
        } catch (Exception $e) {
            echo "Error: " . $e->getMessage();
        }
    }

    private function convertHeadersToArray($headers)
    {
        $headerArray = [];
        foreach ($headers as $key => $value) {
            $headerArray[] = $key . ': ' . $value;
        }
        return $headerArray;
    }


    private function buildCanonicalQueryString($queryParams)
    {

        ksort($queryParams);
        // Build and encode query parameters
        $params = [];
        foreach ($queryParams as $k => $v) {
            if (null === $v) {
                continue;
            }
            $str = rawurlencode($k);
            if ('' !== $v && null !== $v) {
                $str .= '=' . rawurlencode($v);
            } else {
                $str .= '=';
            }
            $params[] = $str;
        }
        return implode('&', $params);
    }

    private function buildCanonicalHeaders($headers)
    {
        // Sort headers by key and concatenate them
        uksort($headers, 'strcasecmp');
        $canonicalHeaders = '';
        foreach ($headers as $key => $value) {
            $canonicalHeaders .= strtolower($key) . ':' . trim($value) . "\n";
        }
        return $canonicalHeaders;
    }

    private function buildSignedHeaders($headers)
    {
        // Build the signed headers string
        $signedHeaders = array_keys($headers);
        sort($signedHeaders, SORT_STRING | SORT_FLAG_CASE);
        return implode(';', array_map('strtolower', $signedHeaders));
    }
}

$demo = new SignatureDemo();
$demo->main();

固定參數示例

本示例是以固定的參數值為例,幫助您驗證簽名是否正確。根據下面假設的固定值計算完簽名之后,得到與本示例一樣的簽名,表明您的簽名過程是正確的。

所需參數名稱

假設的參數值

AccessKeyID

YourAccessKeyId

AccessKeySecret

YourAccessKeySecret

x-acs-signature-nonce

3156853299f313e23d1673dc12e1703d

x-acs-date

2023-10-26T10:22:32Z

簽名流程如下:

  1. 構造規范化請求。

POST
/
ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai
host:ecs.cn-shanghai.aliyuncs.com
x-acs-action:RunInstances
x-acs-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-acs-date:2023-10-26T10:22:32Z
x-acs-signature-nonce:3156853299f313e23d1673dc12e1703d
x-acs-version:2014-05-26

host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
  1. 構造待簽名字符串。

ACS3-HMAC-SHA256
7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259
  1. 計算簽名。

06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0
  1. 將簽名添加到請求中。

POST /?ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai HTTP/1.1
Authorization: ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0
x-acs-action: RunInstances
host: ecs.cn-shanghai.aliyuncs.com
x-acs-date: 2023-10-26T09:01:01Z
x-acs-version: 2014-05-26
x-acs-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-acs-signature-nonce: d410180a5abf7fe235dd9b74aca91fc0
user-agent: AlibabaCloud (Mac OS X; x86_64) Java/1.8.0_352-b08 tea-util/0.2.6 TeaDSL/1
accept: application/json

簽名失敗常見報錯

Code

Message

解決方案

SignatureDoesNotMatch

Specified signature does not match our calculation.

在簽名過程中,您可能遺漏了對參數進行升序排序,也可能是多加了空格。請您仔細閱讀簽名機制的講解,可以根據提供的固定參數示例驗證您的簽名過程是否正確。

IncompleteSignature

The request signature does not conform to Aliyun standards.