全部产品
Search
文档中心

阿里云SDK:V3版本请求体&签名机制

更新时间:May 22, 2024

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. 按照步骤4中的顺序使用与号(&)连接编码后的请求参数。

    重要

    当请求的查询字符串为空时,使用空字符串作为规范化查询字符串。

  • 规范化请求头(CanonicalizedHeaders)

    一个非标准HTTP头部信息。需要将请求中包含以x-acs-为前缀、hostcontent-type的参数信息,添加到规范化请求头中,构造方法如下:

    1. 将所有需要签名的参数的名称转换为小写。

    2. 将所有参数按照参数名称的字符顺序以升序排列。

    3. 将参数的值除去首尾空格。对于有多个值的参数,将多个值分别除去首尾空格后按值升序排列,然后用逗号(,)连接。

    4. 将步骤2、3的结果以英文冒号(:)连接,并在尾部添加换行符,组成一个规范化消息头(CanonicalHeaderEntry)。

    5. 如果没有需要签名的消息头,使用空字符串作为规范化消息头列表。

    重要

    除Authorization外的所有公共请求头,只要符合要求的参数都必须被加入签名。

  • 已签名消息头列表(SignedHeaders

    用于说明此次请求包含了哪些消息头参与签名,与CanonicalHeaders中包含的消息头是一一对应的,构造方法如下:

    • 将CanonicalHeaders中包含的请求头的名称转为小写。

    • 多个请求头名称(小写)按首字母升序排列并以英文分号(;)分隔,例如 content-type;host;x-acs-date

    • 伪代码如下:

      CanonicalHeaderEntry = Lowercase(HeaderName) + ':' + Trim(HeaderValue) + '\n'
      
      CanonicalHeaders = 
          CanonicalHeaderEntry0 + CanonicalHeaderEntry1 + ... + CanonicalHeaderEntryN
  • 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 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
        String canonicalUri = "/api/v1/clusters";
        String host= "cs.cn-beijing.aliyuncs.com"; // endpoint
        String xAcsAction=  "DescribeClustersV1"; // API名称
        String xAcsVersion= "2015-12-15"; // API版本号
        Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);

        request.queryParam.put("cluster_id", "cd4b2c37e17184bc5a81a928eXXXXXXXX");
        request.queryParam.put("cluster_type", "Kubernetes");
        request.queryParam.put("region_id", "cn-beijing");
        request.queryParam.put("name", "testDemo");
        request.queryParam.put("page_size", 10);
        request.queryParam.put("page_number", 1);*/

        // 签名过程
        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) {
                        StringEntity postEntity = new StringEntity(request.body);
                        httpPost.setEntity(postEntity);
                    }
                    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 json
import os
import random
import string
import uuid
from collections import OrderedDict
from urllib.parse import urlencode, quote_plus

import requests
from datetime import datetime, timezone


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
        utc_time = datetime.utcnow().replace(tzinfo=timezone.utc)
        headers['x-acs-date'] = utc_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(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
        canonical_headers = '\n'.join(f'{k.lower()}:{v}' for k, v in request.headers.items() if
                                      k.lower().startswith('x-acs-') or k.lower() in ['host', 'content-type'])
        signed_headers = ';'.join(sorted(request.headers.keys(), key=lambda x: x.lower()))

        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"
    # canonical_uri = "/api/v1/clusters"
    # host = "cs.cn-beijing.aliyuncs.com"
    # x_acs_action = "DescribeClustersV1"
    # x_acs_version = "2015-12-15"
    # request = Request(http_method, canonical_uri, host, x_acs_action, x_acs_version)
    # request.query_param['page_size'] = '10'
    # request.query_param['page_number'] = '1'
    # request.query_param['cluster_id'] = 'cd4b2c37e17184bc5a81a928eXXXXXXXX'
    # request.query_param['cluster_type'] = 'Kubernetes'
    # request.query_param['region_id'] = 'cn-beijing'
    # request.query_param['name'] = 'testDemo'

    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"
	"golang.org/x/exp/maps"
	"io"
	"os"
	"sort"

	"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接口POST请求
		httpMethod := "GET"
		canonicalUri := "/api/v1/clusters"
		host := "cs.cn-beijing.aliyuncs.com"
		xAcsAction := "DescribeClustersV1"
		xAcsVersion := "2015-12-15"
		req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
		req.queryParam["page_size"] = "10"
		req.queryParam["page_number"] = "1"
		req.queryParam["cluster_id"] = "cd4b2c37e17184bc5a81a928eXXXXXXXX"
		req.queryParam["cluster_type"] = "Kubernetes"
		req.queryParam["region_id"] = "cn-beijing"
		req.queryParam["name"] = "testDemo"*/

	req.queryParam = sorted(req.queryParam)
	// 签名过程
	getAuthorization(req)
	// 调用API
	err := callAPI(req)
	if err != nil {
		println(err.Error())
	}
}

func sorted(obj map[string]string) map[string]string {
	keys := maps.Keys(obj)
	sort.Strings(keys)
	newObj := make(map[string]string)
	for _, k := range keys {
		newObj[k] = obj[k]
	}
	return newObj
}

func callAPI(req *Request) error {
	urlStr := "https://" + req.host + req.canonicalUri
	q := url.Values{}
	for k, v := range req.queryParam {
		q.Set(k, v)
	}
	urlStr += "?" + q.Encode()

	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 {
		panic(err)
		return err
	}
	respBytes := respBuffer.Bytes()
	fmt.Println(string(respBytes))
	return nil
}

func getAuthorization(req *Request) {
	canonicalQueryString := ""
	for k, v := range req.queryParam {
		canonicalQueryString += percentCode(url.QueryEscape(k)) + "=" + percentCode(url.QueryEscape(v)) + "&"
	}
	canonicalQueryString = strings.TrimSuffix(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。在运行示例前,请在终端(Terminal)执行以下命令下载所需要的module。

 npm install --save-dev @types/node
const crypto = require('crypto');
const {stringify} = require("node:querystring");
const {request} = require("node:https");

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';
// const canonicalUri = '/api/v1/clusters';
// const host = 'cs.cn-beijing.aliyuncs.com';
// const xAcsAction = 'DescribeClustersV1';
// const xAcsVersion = '2015-12-15';
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// signRequest.queryParam = {
//     cluster_id: 'cd4b2c37e17184bc5a81a928eXXXXXXXX',
//     cluster_type: 'Kubernetes',
//     region_id: 'cn-beijing',
//     name: 'testDemo',
//     page_size: '10',
//     page_number: '1',
// }

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

固定参数示例

本示例是以固定的参数值为例,帮助您验证签名是否正确。根据下面假设的固定值计算完签名之后,得到与本示例一样的签名,表明您的签名过程是正确的。

所需参数名称

假设的参数值

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.