すべてのプロダクト
Search
ドキュメントセンター

Alibaba Cloud SDK:v3 リクエストボディと署名メカニズム

最終更新日:Nov 09, 2025

SDK を使用せずに Alibaba Cloud OpenAPI を呼び出したい場合、またはアプリケーションのランタイム環境が SDK をサポートしていない場合は、リクエストに自己署名することで Alibaba Cloud OpenAPI を呼び出すことができます。このトピックでは、HTTP リクエストを使用して Alibaba Cloud OpenAPI を直接呼び出すのに役立つ v3 署名メカニズムについて説明します。

使用上の注意

  • API 呼び出しでは、v2 署名を v3 署名に直接置き換えることができます。

  • OpenAPI ポータルは、Alibaba Cloud プロダクトの SDK を提供します。これらのプロダクトの API は v3 署名をサポートしています。一部のクラウドプロダクトは自己管理ゲートウェイを使用しており、このトピックで説明されているものとは異なる認証メカニズムを持っていることに注意してください。これらのプロダクトに HTTP リクエストを送信する場合は、それぞれの署名メカニズムのドキュメントをご参照ください。

HTTP リクエスト構造

完全な Alibaba Cloud OpenAPI リクエストは、次の部分で構成されます。

名前

必須

説明

プロトコル

はい

これは、さまざまなクラウドプロダクトの API リファレンスを参照して構成できます。リクエストは HTTP または HTTPS 経由で送信できます。セキュリティを向上させるには、HTTPS を使用してください。有効な値は https:// または http:// です。

https://

エンドポイント

はい

エンドポイント。エンドポイントがデプロイされているさまざまなエリアのエンドポイントは、各クラウドプロダクトのエンドポイントドキュメントで確認できます。

ecs.cn-shanghai.aliyuncs.com

resource_URI_parameters

はい

API パスと、パスおよびクエリにあるリクエストパラメーターを含む API URL です。

ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai

RequestHeader

はい

共通リクエストヘッダー。通常、API バージョン、Host、および権限付与情報が含まれます。これについては後で詳しく説明します。

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

RequestBody

はい

ボディで定義されたビジネスリクエストパラメーター。API メタデータから取得できます。

HTTPMethod

はい

リクエストメソッド。API メタデータから取得できます。

POST

RequestHeader

Alibaba Cloud OpenAPI を呼び出すとき、共通リクエストヘッダーには次の情報を含める必要があります。

名前

タイプ

必須

説明

host

文字列

はい

エンドポイント。詳細については、「HTTP リクエスト構造」をご参照ください。

ecs.cn-shanghai.aliyuncs.com

x-acs-action

文字列

はい

API の名前。呼び出したい OpenAPI を検索するには、Alibaba Cloud OpenAPI Developer Portal にアクセスしてください。

RunInstances

x-acs-content-sha256

文字列

はい

RequestBody をハッシュ化し、Base16 エンコーディングした結果。この値は HashedRequestPayload と同じです。

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

x-acs-date

文字列

はい

ISO 8601 形式の UTC 時刻: yyyy-MM-ddTHH:mm:ssZ。例: 2018-01-01T12:00:00Z。値は、リクエストが送信される 15 分以内の時刻である必要があります。

2023-10-26T10:22:32Z

x-acs-signature-nonce

文字列

はい

署名用の一意の乱数。この番号はリプレイ攻撃を防ぎます。リクエストごとに異なる乱数を使用してください。このメカニズムは HTTP プロトコルにのみ適用されます。

3156853299f313e23d1673dc12e1703d

x-acs-version

文字列

はい

API バージョン。バージョンの取得方法の詳細については、「API バージョン (x-acs-version) を取得するにはどうすればよいですか?」をご参照ください。

2014-05-26

Authorization

文字列

匿名でないリクエストに必須

リクエストの正当性を検証するために使用される認証情報。フォーマットは Authorization: SignatureAlgorithm Credential=AccessKeyId,SignedHeaders=SignedHeaders,Signature=Signature です。

SignatureAlgorithm は署名暗号化メソッドで、ACS3-HMAC-SHA256 です。

Credential はユーザーの AccessKey ID です。RAM コンソールで AccessKey ID を表示できます。AccessKey ペアを作成するには、「AccessKey ペアの作成」をご参照ください。

SignedHeaders は、署名に含まれるリクエストヘッダーのキーを指定します。注意: セキュリティを向上させるため、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=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0

x-acs-security-token

文字列

STS 認証に必須

AssumeRole 操作を呼び出して返された応答の SecurityToken の値。

署名メカニズム

署名は AccessKey ペアを使用して認証されます。各 HTTP または HTTPS リクエストに対して、Alibaba Cloud API Gateway はリクエストパラメーターに基づいて署名を再計算します。ゲートウェイは、この署名をリクエストで提供された署名と比較して、リクエスターの ID を検証します。このプロセスにより、データの整合性とセキュリティが保証されます。

重要

リクエストと応答は UTF-8 文字セットでエンコードされます。

ステップ 1: 正規リクエストを作成する

次の擬似コードは、正規リクエスト (CanonicalRequest) を作成する方法を示しています:

CanonicalRequest =
  HTTPRequestMethod + '\n' +    // HTTP リクエストメソッド、大文字
  CanonicalURI + '\n' +         // 正規 URI
  CanonicalQueryString + '\n' + // 正規クエリ文字列
  CanonicalHeaders + '\n' +     // 正規ヘッダー
  SignedHeaders + '\n' +        // 署名付きヘッダー
  HashedRequestPayload		// ハッシュ化された RequestBody の値

HTTPRequestMethod (リクエストメソッド)

大文字の HTTP メソッド名 (GET や POST など)。

CanonicalURI (正規 URI)

正規 URI は、URL のエンコードされたリソースパスです。リソースパスは、URL のホストとクエリ文字列の間の部分です。ホストの後の / を含みますが、クエリ文字列の前の ? は含みません。URI の各部分 (/ で区切られた文字列) を、RFC3986 のルールに従って UTF-8 文字セットを使用してエンコードする必要があります:

  • 文字 A-Z、a-z、0-9、および文字 -_.~ はエンコードされません。

    その他の文字は、% の後に文字の ASCII コードを 16 進数形式で続けたものとしてエンコードされます。たとえば、半角の二重引用符 (") は %22 としてエンコードされます。一部の特殊文字には特別な処理が必要なことに注意してください。

    エンコード前

    エンコード後

    スペース ( )

    %20

    アスタリスク (*)

    %2A

    %7E

    チルダ (~)

Java 標準ライブラリの java.net.URLEncoder を使用する場合、まず encode メソッドを使用して文字列をエンコードできます。次に、プラス記号 (+) を %20 に、アスタリスク (*) を %2A に、%7E をチルダ (~) に置き換えて、前述のルールに準拠したエンコード済み文字列を取得する必要があります。

重要

RPC スタイルの API の場合、正規 URI としてスラッシュ (/) を使用します。

ROA スタイルの API の場合、このパラメーターは OpenAPI メタデータの path パラメーターの値です。例: /api/v1/clusters

CanonicalQueryString (正規クエリ文字列)

API メタデータで、リクエストパラメーターがクエリ内 ("in":"query") にあると指定されている場合、次のようにすべてのパラメーターを連結する必要があります:

  1. リクエストパラメーターを名前のアルファベット昇順でソートします。

  2. 各パラメーター名と値を、RFC3986 のルールに従って UTF-8 文字セットを使用して URI エンコードします。エンコーディングルールは、正規 URI のエンコーディングルールと同じです。

  3. エンコードされたパラメーター名と値を等号 (=) で接続します。パラメーターに値がない場合は、その値として空の文字列を使用します。

  4. 複数のリクエストパラメーターをアンパサンド (&) で接続します。

重要
  • リクエストパラメーターが配列またはオブジェクト型の場合、パラメーター値をインデックス付きのキーと値のペアに変換する必要があります。詳細については、「配列またはオブジェクト型のパラメーターを渡すにはどうすればよいですか?」をご参照ください。

  • リクエストパラメーターが JSON 文字列の場合、JSON 文字列内のパラメーターの順序は署名計算に影響しません。

  • クエリ文字列が存在しない場合は、正規クエリ文字列として空の文字列を使用します。

例:

ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai

HashedRequestPayload

リクエストボディをハッシュ化し、そのハッシュ値を Base16 エンコードして、ハッシュ化されたリクエストペイロードを取得します。リクエストヘッダーx-acs-content-sha256 ヘッダーの値を、ハッシュ化されたリクエストペイロードの値に更新します。次の擬似コードは、ハッシュ化されたリクエストペイロードを計算する方法を示しています:

HashedRequestPayload = HexEncode(Hash(RequestBody))
  • API メタデータで、API のリクエストパラメーターが "in": "body" または "in": "formData" に設定されている場合、RequestBody でパラメーターを渡す必要があります:

    説明

    リクエストボディでリクエストパラメーターが渡されない場合、リクエストボディは空の文字列になります。

    • リクエストパラメーターに "in": "formData" が含まれている場合、パラメーターを key1=value1&key2=value2&key3=value3 の形式の文字列に連結する必要があります。また、リクエストヘッダーに content-type=application/x-www-form-urlencoded を追加する必要があります。リクエストパラメーターが配列またはオブジェクトの場合は、パラメーター値をインデックス付きのキーと値のペアに変換する必要があります

    • リクエストパラメーターに "in": "body" が含まれている場合、リクエストに `Content-Type` ヘッダーを追加する必要があります。このヘッダーの値は、リクエストのコンテンツタイプによって異なります。例:

      • リクエストコンテンツが JSON データの場合、content-type は application/json です。

      • リクエストコンテンツがバイナリファイルストリームの場合、content-type は application/octet-stream です。

  • Hash はメッセージダイジェスト関数を表します。SHA256 アルゴリズムのみがサポートされています。

  • HexEncode は、ダイジェストを小文字の 16 進数形式 (Base16 エンコーディング) で返すエンコーディング関数を表します。

次の例は、リクエストボディが空の場合のハッシュ化されたリクエストペイロードの値を示しています:

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

CanonicalHeaders (正規リクエストヘッダー)

リクエストヘッダーのパラメーターを次のように連結します:

  1. リクエストヘッダーをフィルタリングして、x-acs- プレフィックスを持つか、host または content-type という名前のパラメーターを選択します。

  2. パラメーター名を小文字に変換し、アルファベット順にソートします。

  3. パラメーター値の先頭と末尾のスペースをトリミングします。

  4. パラメーター名と値をコロン (:) で接続し、末尾に改行 (\n) を追加して、正規ヘッダーエントリを形成します。

  5. 複数の正規ヘッダーエントリを 1 つの文字列に連結します。

説明

Authorization ヘッダーを除くすべてのリクエストヘッダーは、要件を満たしている場合、署名に含める必要があります。

擬似コードは次のとおりです:

CanonicalHeaderEntry = Lowercase(HeaderName) + ':' + Trim(HeaderValue) + '\n'

CanonicalHeaders = 
    CanonicalHeaderEntry0 + CanonicalHeaderEntry1 + ... + CanonicalHeaderEntryN

例:

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

SignedHeaders (署名付きヘッダーのリスト)

このパラメーターは、リクエストの署名に含まれる共通リクエストヘッダーを指定します。これは、正規ヘッダーのパラメーター名に対応します。署名付きヘッダーのリストを次のように作成します:

  • 正規ヘッダーに含まれるヘッダーの名前を小文字に変換します。

  • ヘッダー名をアルファベット順にソートし、セミコロン (;) で区切ります。

擬似コードは次のとおりです:

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

例:

host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version

ステップ 2: 署名文字列を作成する

次の擬似コードに基づいて、署名文字列 (stringToSign) を作成します:

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

    署名プロトコルは ACS3-HMAC-SHA256 アルゴリズムのみをサポートします。

  • HashedCanonicalRequest

    ハッシュ化された正規リクエスト文字列。次の擬似コードは、ハッシュ化された正規リクエストを計算する方法を示しています:

    HashedCanonicalRequest = HexEncode(Hash(CanonicalRequest))
    • Hash はメッセージダイジェスト関数を表します。SHA256 アルゴリズムのみがサポートされています。

    • HexEncode は、ダイジェストを小文字の 16 進数形式 (Base16 エンコーディング) で返すエンコーディング関数を表します。

例:

ACS3-HMAC-SHA256
7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259

ステップ 3: 署名を計算する

次の擬似コードに基づいて、署名値 (Signature) を計算します。

Signature = HexEncode(SignatureMethod(Secret, StringToSign))
  • StringToSign: ステップ 2 で作成され、UTF-8 でエンコードされた署名文字列。

  • SignatureMethod: 署名アルゴリズムとして HMAC-SHA256 を使用します。

  • Secret: AccessKey Secret。

  • HexEncode: ダイジェストを小文字の 16 進数形式 (Base16 エンコーディング) で返すエンコーディング関数。

例:

06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0

ステップ 4: 署名をリクエストに追加する

署名を計算した後、次の形式で Authorization リクエストヘッダーを作成します: Authorization: SignatureAlgorithm Credential=AccessKeyId,SignedHeaders=SignedHeaders,Signature=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=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0

署名サンプルコード

署名メカニズムをよりよく理解していただくために、このトピックでは主要なプログラミング言語での完全な実装を提供します。サンプルコードは署名メカニズムを理解するのに役立つものであり、普遍的に適用できるとは限りません。Alibaba Cloud OpenAPI は、さまざまなプログラミング言語と開発フレームワーク用の SDK を提供しています。これらの SDK を使用する場合、署名プロセスを実行する必要はありません。これにより、Alibaba Cloud に関連するアプリケーションを迅速に構築できます。SDK を使用することをお勧めします。

重要

リクエストに署名する前に、必ず API メタデータを確認して、API のリクエストメソッド、リクエストパラメーター名、リクエストパラメーターの型、パラメーターの渡し方などの情報を取得してください。そうしないと、署名が失敗する可能性があります。

固定パラメーターの例

この例では、仮定のパラメーター値を使用して、署名メカニズムの各ステップでの正しい出力を示します。この例の仮定のパラメーター値をコードで計算に使用し、出力をこの例の内容と比較して、署名プロセスが正しいことを確認できます。

必須パラメーター名

仮定のパラメーター値

AccessKeyID

YourAccessKeyId

AccessKeySecret

YourAccessKeySecret

x-acs-signature-nonce

3156853299f313e23d1673dc12e1703d

x-acs-date

2023-10-26T10:22:32Z

x-acs-action

RunInstances

x-acs-version

2014-05-26

host

ecs.cn-shanghai.aliyuncs.com

API リクエストパラメーター:

ImageId

win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd

RegionId

cn-shanghai

署名フローは次のとおりです:

  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

Java の例

説明

サンプルコードは JDK 1.8 で実行されます。特定の状況に応じてコードを調整する必要がある場合があります。

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.ByteArrayEntity;
import org.apache.http.entity.ContentType;
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.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

public class SignatureDemo {

    public static class SignatureRequest {
        // HTTP メソッド
        private final String httpMethod;
        // リクエストパス
        private final String canonicalUri;
        // エンドポイント
        private final String host;
        // API 名
        private final String xAcsAction;
        // API バージョン
        private final String xAcsVersion;
        // ヘッダー
        private final Map<String, String> headers = new TreeMap<>();
        // ボディパラメーター
        private byte[] body;
        // クエリパラメーター
        private final Map<String, Object> queryParam = new TreeMap<>();

        public SignatureRequest(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;
            initHeader();
        }

        private void initHeader() {
            headers.put("host", host);
            headers.put("x-acs-action", xAcsAction);
            headers.put("x-acs-version", xAcsVersion);

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
            sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
            headers.put("x-acs-date", sdf.format(new Date()));
            headers.put("x-acs-signature-nonce", UUID.randomUUID().toString());
        }

        public String getHttpMethod() {
            return httpMethod;
        }

        public String getCanonicalUri() {
            return canonicalUri;
        }

        public String getHost() {
            return host;
        }

        public Map<String, String> getHeaders() {
            return headers;
        }

        public byte[] getBody() {
            return body;
        }

        public Map<String, Object> getQueryParam() {
            return queryParam;
        }

        public void setBody(byte[] body) {
            this.body = body;
        }

        public void setQueryParam(String key, Object value) {
            this.queryParam.put(key, value);
        }

        public void setHeaders(String key, String value) {
            this.headers.put(key, value);
        }
    }

    public static class SignatureService {
        private static final String ALGORITHM = "ACS3-HMAC-SHA256";

        /**
         * 署名を計算する
         */
        public static void getAuthorization(SignatureRequest signatureRequest,
                                            String accessKeyId, String accessKeySecret, String securityToken) {
            try {
                // 複雑なクエリパラメーターを処理する
                Map<String, Object> processedQueryParams = new TreeMap<>();
                processObject(processedQueryParams, "", signatureRequest.getQueryParam());
                signatureRequest.getQueryParam().clear();
                signatureRequest.getQueryParam().putAll(processedQueryParams);

                // ステップ 1: 正規リクエスト文字列を作成する
                String canonicalQueryString = buildCanonicalQueryString(signatureRequest.getQueryParam());

                // リクエストボディのハッシュを計算する
                String hashedRequestPayload = calculatePayloadHash(signatureRequest.getBody());
                signatureRequest.setHeaders("x-acs-content-sha256", hashedRequestPayload);

                // セキュリティトークンが存在する場合は追加する
                if (securityToken != null && !securityToken.isEmpty()) {
                    signatureRequest.setHeaders("x-acs-security-token", securityToken);
                }

                // 正規ヘッダーと署名付きヘッダーを構築する
                CanonicalHeadersResult canonicalHeadersResult = buildCanonicalHeaders(signatureRequest.getHeaders());

                // 正規リクエストを構築する
                String canonicalRequest = String.join("\n",
                        signatureRequest.getHttpMethod(),
                        signatureRequest.getCanonicalUri(),
                        canonicalQueryString,
                        canonicalHeadersResult.canonicalHeaders,
                        canonicalHeadersResult.signedHeaders,
                        hashedRequestPayload);

                System.out.println("canonicalRequest=========>\n" + canonicalRequest);

                // ステップ 2: 署名文字列を作成する
                String hashedCanonicalRequest = sha256Hex(canonicalRequest.getBytes(StandardCharsets.UTF_8));
                String stringToSign = ALGORITHM + "\n" + hashedCanonicalRequest;
                System.out.println("stringToSign=========>\n" + stringToSign);

                // ステップ 3: 署名を計算する
                String signature = DatatypeConverter.printHexBinary(
                                hmac256(accessKeySecret.getBytes(StandardCharsets.UTF_8), stringToSign))
                        .toLowerCase();
                System.out.println("signature=========>" + signature);

                // ステップ 4: Authorization ヘッダーを構築する
                String authorization = String.format("%s Credential=%s,SignedHeaders=%s,Signature=%s",
                        ALGORITHM, accessKeyId, canonicalHeadersResult.signedHeaders, signature);

                System.out.println("authorization=========>" + authorization);
                signatureRequest.getHeaders().put("Authorization", authorization);
            } catch (Exception e) {
                throw new RuntimeException("Failed to generate authorization", e);
            }
        }

        /**
         * formData リクエストパラメータータイプのパラメーターを処理する。
         */
        private static String formDataToString(Map<String, Object> formData) {
            Map<String, Object> tileMap = new HashMap<>();
            processObject(tileMap, "", formData);
            StringBuilder result = new StringBuilder();
            boolean first = true;
            String symbol = "&";
            for (Map.Entry<String, Object> entry : tileMap.entrySet()) {
                String value = String.valueOf(entry.getValue());
                if (value != null && !value.isEmpty()) {
                    if (first) {
                        first = false;
                    } else {
                        result.append(symbol);
                    }
                    result.append(percentCode(entry.getKey()));
                    result.append("=");
                    result.append(percentCode(value));
                }
            }

            return result.toString();
        }

        /**
         * 正規クエリ文字列を構築する
         */
        private static String buildCanonicalQueryString(Map<String, Object> queryParams) {
            return queryParams.entrySet().stream()
                    .map(entry -> percentCode(entry.getKey()) + "=" +
                            percentCode(String.valueOf(entry.getValue())))
                    .collect(Collectors.joining("&"));
        }

        /**
         * リクエストボディのハッシュ値を計算する
         */
        private static String calculatePayloadHash(byte[] body) throws Exception {
            if (body != null) {
                return sha256Hex(body);
            } else {
                return sha256Hex("".getBytes(StandardCharsets.UTF_8));
            }
        }

        /**
         * 正規ヘッダー情報を構築する
         */
        private static CanonicalHeadersResult buildCanonicalHeaders(Map<String, String> headers) {
            List<Map.Entry<String, String>> signedHeaders = headers.entrySet().stream()
                    .filter(entry -> {
                        String key = entry.getKey().toLowerCase();
                        return key.startsWith("x-acs-") || "host".equals(key) || "content-type".equals(key);
                    })
                    .sorted(Map.Entry.comparingByKey())
                    .collect(Collectors.toList());

            StringBuilder canonicalHeaders = new StringBuilder();
            StringBuilder signedHeadersString = new StringBuilder();

            for (Map.Entry<String, String> entry : signedHeaders) {
                String lowerKey = entry.getKey().toLowerCase();
                String value = entry.getValue().trim();
                canonicalHeaders.append(lowerKey).append(":").append(value).append("\n");
                signedHeadersString.append(lowerKey).append(";");
            }

            if (signedHeadersString.length() > 0) {
                signedHeadersString.setLength(signedHeadersString.length() - 1); // 最後のセミコロンを削除
            }

            return new CanonicalHeadersResult(canonicalHeaders.toString(), signedHeadersString.toString());
        }

        private static class CanonicalHeadersResult {
            final String canonicalHeaders;
            final String signedHeaders;

            CanonicalHeadersResult(String canonicalHeaders, String signedHeaders) {
                this.canonicalHeaders = canonicalHeaders;
                this.signedHeaders = signedHeaders;
            }
        }

        /**
         * 複雑なオブジェクトパラメーターを処理する
         */
        private static void processObject(Map<String, Object> map, String key, Object value) {
            if (value == null) {
                return;
            }

            if (key == null) {
                key = "";
            }

            if (value instanceof List<?>) {
                List<?> list = (List<?>) value;
                for (int i = 0; i < list.size(); ++i) {
                    processObject(map, key + "." + (i + 1), list.get(i));
                }
            } else if (value instanceof Map<?, ?>) {
                Map<?, ?> subMap = (Map<?, ?>) value;
                for (Map.Entry<?, ?> entry : subMap.entrySet()) {
                    processObject(map, key + "." + entry.getKey().toString(), entry.getValue());
                }
            } else {
                if (key.startsWith(".")) {
                    key = key.substring(1);
                }

                if (value instanceof byte[]) {
                    map.put(key, new String((byte[]) value, StandardCharsets.UTF_8));
                } else {
                    map.put(key, String.valueOf(value));
                }
            }
        }

        /**
         * HMAC-SHA256 計算
         */
        private static byte[] hmac256(byte[] secretKey, String str) throws Exception {
            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, mac.getAlgorithm());
            mac.init(secretKeySpec);
            return mac.doFinal(str.getBytes(StandardCharsets.UTF_8));
        }

        /**
         * SHA-256 ハッシュ計算
         */
        private static String sha256Hex(byte[] input) throws Exception {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] digest = md.digest(input);
            return DatatypeConverter.printHexBinary(digest).toLowerCase();
        }

        /**
         * URL エンコーディング
         */
        public static String percentCode(String str) {
            if (str == null) {
                return "";
            }
            try {
                return URLEncoder.encode(str, "UTF-8")
                        .replace("+", "%20")
                        .replace("*", "%2A")
                        .replace("%7E", "~");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("UTF-8 encoding not supported", e);
            }
        }
    }

    /**
     * 署名の例。必要に応じて main メソッドの例のパラメーターを置き換えてください。
     * canonicalUri 値を取得するロジックは、ROA API と RPC API の唯一の違いです。残りは同様です。
     * <p>
     * API メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得し、パラメーターを SignatureRequest にカプセル化します。
     * 1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、queryParam を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
     * 2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/octet-stream または application/json です。注意: RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
     * 3. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/x-www-form-urlencoded です。
     */
    public static void main(String[] args) throws IOException {
        // 環境変数から AccessKey を取得
        String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
        String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
        String securityToken = System.getenv("ALIBABA_CLOUD_SECURITY_TOKEN");

        if (accessKeyId == null || accessKeySecret == null) {
            System.err.println("Set the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables.");
            return;
        }

        // RPC API の例 1: リクエストパラメーターは "query" にあります。この例では、ECS の DescribeInstanceStatus を使用します。
        SignatureRequest signatureRequest = new SignatureRequest(
                "POST",
                "/",
                "ecs.cn-hangzhou.aliyuncs.com",
                "DescribeInstanceStatus",
                "2014-05-26"
        );
        signatureRequest.setQueryParam("RegionId", "cn-hangzhou");
        signatureRequest.setQueryParam("InstanceId", Arrays.asList("i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"));

        /*// RPC API の例 2: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ)。この例では、OCR の RecognizeGeneral を使用します。
        SignatureRequest signatureRequest = new SignatureRequest(
                "POST",
                "/",
                "ocr-api.cn-hangzhou.aliyuncs.com",
                "RecognizeGeneral",
                "2021-07-07");
        signatureRequest.setBody(Files.readAllBytes(Paths.get("D:\\test.jpeg")));
        signatureRequest.setHeaders("content-type", "application/octet-stream");*/

        /*// RPC API の例 3: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ)。この例では、Machine Translation の TranslateGeneral を使用します。
        String httpMethod = "POST";
        String canonicalUri = "/";
        String host = "mt.aliyuncs.com";
        String xAcsAction = "TranslateGeneral";
        String xAcsVersion = "2018-10-12";
        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
        Map<String, Object> body = new HashMap<>();
        body.put("FormatType", "text");
        body.put("SourceLanguage", "zh");
        body.put("TargetLanguage", "en");
        body.put("SourceText", "Hello");
        body.put("Scene", "general");
        String formDataToString = SignatureService.formDataToString(body);
        signatureRequest.setBody(formDataToString.getBytes(StandardCharsets.UTF_8));
        signatureRequest.setHeaders("content-type", "application/x-www-form-urlencoded");*/

        /*// ROA API POST リクエストの例。この例では、Container Service でクラスターを作成します。
        SignatureRequest signatureRequest = new SignatureRequest(
                "POST",
                "/clusters",
                "cs.cn-chengdu.aliyuncs.com",
                "CreateCluster",
                "2015-12-15");
        TreeMap<String, Object> body = new TreeMap<>();
        body.put("name", "test");
        body.put("cluster_type", "ManagedKubernetes");
        body.put("kubernetes_version", "1.34.1-aliyun.1");
        body.put("region_id", "cn-chengdu");
        body.put("snat_entry", true);
        body.put("deletion_protection", true);
        body.put("proxy_mode", "ipvs");
        body.put("profile", "Default");
        body.put("timezone", "Asia/Shanghai");
        body.put("cluster_spec", "ack.pro.small");
        body.put("enable_rrsa", false);
        body.put("service_cidr", "192.168.0.0/16");
        body.put("zone_ids", Arrays.asList("cn-chengdu-b","cn-chengdu-b"));
        Gson gson = (new GsonBuilder()).disableHtmlEscaping().create();
        signatureRequest.setBody(gson.toJson(body).getBytes(StandardCharsets.UTF_8));
        signatureRequest.setHeaders("content-type", "application/json");*/

        /*// ROA API GET リクエスト。この例では、Container Service のクラスター情報をクエリします。
        SignatureRequest signatureRequest = new SignatureRequest(
                "GET",
                "/clusters/" + SignatureService.percentCode("c299f90b63b************") + "/resources",
                "cs.cn-chengdu.aliyuncs.com",
                "DescribeClusterResources",
                "2015-12-15");
        signatureRequest.setQueryParam("with_addon_resources", true);*/

        /*// ROA API DELETE リクエスト。この例では、クラスターを削除します。
        SignatureRequest signatureRequest = new SignatureRequest(
                "DELETE",
                "/clusters/" + SignatureService.percentCode("c299f90b63b************"),
                "cs.cn-chengdu.aliyuncs.com",
                "DeleteCluster",
                "2015-12-15");*/

        // 署名を生成する
        SignatureService.getAuthorization(signatureRequest, accessKeyId, accessKeySecret, securityToken);

        // API が正常に呼び出せるかテストする
        callApi(signatureRequest);
    }

    /**
     * テスト専用
     */
    private static void callApi(SignatureRequest signatureRequest) {
        try {
            String url = "https://" + signatureRequest.getHost() + signatureRequest.getCanonicalUri();
            URIBuilder uriBuilder = new URIBuilder(url);

            // クエリパラメーターを追加する
            for (Map.Entry<String, Object> entry : signatureRequest.getQueryParam().entrySet()) {
                uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue()));
            }
            HttpUriRequest httpRequest;
            switch (signatureRequest.getHttpMethod()) {
                case "GET":
                    httpRequest = new HttpGet(uriBuilder.build());
                    break;
                case "POST":
                    HttpPost httpPost = new HttpPost(uriBuilder.build());
                    if (signatureRequest.getBody() != null) {
                        httpPost.setEntity(new ByteArrayEntity(signatureRequest.getBody(), ContentType.create(signatureRequest.getHeaders().get("content-type"))));
                    }
                    httpRequest = httpPost;
                    break;
                case "DELETE":
                    httpRequest = new HttpDelete(uriBuilder.build());
                    break;
                default:
                    System.out.println("Unsupported HTTP method: " + signatureRequest.getHttpMethod());
                    throw new IllegalArgumentException("Unsupported HTTP method");
            }

            // リクエストヘッダーを追加する
            for (Map.Entry<String, String> entry : signatureRequest.getHeaders().entrySet()) {
                httpRequest.addHeader(entry.getKey(), entry.getValue());
            }

            // リクエストを送信する
            try (CloseableHttpClient httpClient = HttpClients.createDefault();
                 CloseableHttpResponse response = httpClient.execute(httpRequest)) {
                String result = EntityUtils.toString(response.getEntity(), "UTF-8");
                System.out.println("API Response: " + result);
            }
        } catch (IOException | URISyntaxException e) {
            throw new RuntimeException("Failed to call API", e);
        }
    }
}

Python の例

説明

サンプルコードは Python 3.12.3 で実行されます。特定の状況に応じてコードを調整する必要がある場合があります。

pytz と requests を手動でインストールする必要があります。Python のバージョンに応じて、ターミナルで次のコマンドを実行します。

Python 3

pip3 install pytz
pip3 install requests
import hashlib
import hmac
import json
import os
import uuid
from collections import OrderedDict
from datetime import datetime
from typing import Any, Dict, List, Optional, Union
from urllib.parse import quote_plus, urlencode

import pytz
import requests


class SignatureRequest:
    def __init__(
            self,
            http_method: str,
            canonical_uri: str,
            host: str,
            x_acs_action: str,
            x_acs_version: str
    ):
        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()  # type: Dict[str, Any]
        self.body = None  # type: Optional[bytes]

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

    def sorted_query_params(self) -> None:
        """クエリパラメーターを名前でソートし、エンコードされた文字列を返します。"""
        self.query_param = dict(sorted(self.query_param.items()))

    def sorted_headers(self) -> None:
        """リクエストヘッダーを名前でソートし、エンコードされた文字列を返します。"""
        self.headers = dict(sorted(self.headers.items()))


def get_authorization(request: SignatureRequest) -> None:
    try:
        new_query_param = OrderedDict()
        process_object(new_query_param, '', request.query_param)
        request.query_param.clear()
        request.query_param.update(new_query_param)
        request.sorted_query_params()

        # ステップ 1: 正規リクエスト文字列を作成する
        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 b'')
        request.headers['x-acs-content-sha256'] = hashed_request_payload

        if SECURITY_TOKEN:
            signature_request.headers["x-acs-security-token"] = SECURITY_TOKEN
        request.sorted_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()) + "\n"
        signed_headers = ";".join(filtered_headers.keys())

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

        # ステップ 2: 署名文字列を作成する
        hashed_canonical_request = sha256_hex(canonical_request.encode("utf-8"))
        string_to_sign = f"{ALGORITHM}\n{hashed_canonical_request}"
        print(string_to_sign)

        # ステップ 3: 署名を計算する
        signature = hmac256(ACCESS_KEY_SECRET.encode("utf-8"), string_to_sign).hex().lower()

        # ステップ 4: Authorization ヘッダーを構築する
        authorization = f'{ALGORITHM} Credential={ACCESS_KEY_ID},SignedHeaders={signed_headers},Signature={signature}'
        request.headers["Authorization"] = authorization
    except Exception as e:
        print("Failed to get authorization")
        print(e)


def form_data_to_string(form_data: Dict[str, Any]) -> str:
    tile_map = OrderedDict()
    process_object(tile_map, "", form_data)
    return urlencode(tile_map)


def process_object(result_map: Dict[str, str], key: str, value: Any) -> None:
    if value is None:
        return

    if isinstance(value, (list, tuple)):
        for i, item in enumerate(value):
            process_object(result_map, f"{key}.{i + 1}", item)
    elif isinstance(value, dict):
        for sub_key, sub_value in value.items():
            process_object(result_map, f"{key}.{sub_key}", sub_value)
    else:
        key = key.lstrip(".")
        result_map[key] = value.decode("utf-8") if isinstance(value, bytes) else str(value)


def hmac256(key: bytes, msg: str) -> bytes:
    return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()


def sha256_hex(s: bytes) -> str:
    return hashlib.sha256(s).hexdigest()


def call_api(request: SignatureRequest) -> None:
    url = f"https://{request.host}{request.canonical_uri}"
    if request.query_param:
        url += "?" + urlencode(request.query_param, doseq=True, safe="*")

    headers = dict(request.headers)
    data = request.body

    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: str) -> str:
    return encoded_str.replace("+", "%20").replace("*", "%2A").replace("%7E", "~")


# 環境変数から 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")
SECURITY_TOKEN = os.environ.get("ALIBABA_CLOUD_SECURITY_TOKEN")

ALGORITHM = "ACS3-HMAC-SHA256"

"""
署名の例。テスト時には、main 関数から例を選択し、必要に応じて例の値を変更できます。たとえば、SendSms を呼び出すには、例 1 を選択し、http_method、host、x_acs_action、x_acs_version、および query_param を変更します。
canonicalUri 値を取得するロジックは、ROA API と RPC API の唯一の違いです。

OpenAPI メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得し、パラメーターを SignatureRequest にカプセル化します。
1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、content-type を設定せずに queryParam を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡し、必要に応じて content-type を設定します。注意: RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
3. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、content-type を application/x-www-form-urlencoded としてボディ経由でパラメーターを渡します。
"""
if __name__ == "__main__":
    # RPC API リクエストの例 1: リクエストパラメーターは "query" にあります
    http_method = "POST"  # リクエストメソッド。メタデータから取得できます。POST を推奨します。
    canonical_uri = "/"  # RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
    host = "ecs.cn-hangzhou.aliyuncs.com"  # クラウドプロダクトのエンドポイント
    x_acs_action = "DescribeInstanceStatus"  # API 名
    x_acs_version = "2014-05-26"  # API バージョン番号
    signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
    # DescribeInstanceStatus リクエストパラメーター:
    # RegionId はメタデータで String 型、"in":"query"、必須
    signature_request.query_param['RegionId'] = 'cn-hangzhou'
    # InstanceId はメタデータで array 型、"in":"query"、必須ではない
    signature_request.query_param['InstanceId'] = ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX",
                                                   "i-bp1incuofvzxXXXXXXXX"]

    # # RPC API リクエストの例 2: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ)
    # http_method = "POST"
    # canonical_uri = "/"
    # host = "ocr-api.cn-hangzhou.aliyuncs.com"
    # x_acs_action = "RecognizeGeneral"
    # x_acs_version = "2021-07-07"
    # signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
    # # リクエストパラメーターはメタデータで "in": "body" と表示され、ボディ経由で渡されます。
    # file_path = "D:\\test.png"
    # with open(file_path, 'rb') as file:
    #     # イメージコンテンツをバイト配列として読み取る
    #     signature_request.body = file.read()
    #     signature_request.headers["content-type"] = "application/octet-stream"

    # # RPC API リクエストの例 3: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ)
    # http_method = "POST"
    # canonical_uri = "/"
    # host = "mt.aliyuncs.com"
    # x_acs_action = "TranslateGeneral"
    # x_acs_version = "2018-10-12"
    # signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
    # # TranslateGeneral リクエストパラメーター:
    # # Context はメタデータで String 型、"in":"query"、必須ではない
    # signature_request.query_param['Context'] = 'Morning'
    # # FormatType、SourceLanguage、TargetLanguage などのパラメーターはメタデータで "in":"formData" と表示されます
    # form_data = OrderedDict()
    # form_data["FormatType"] = "text"
    # form_data["SourceLanguage"] = "zh"
    # form_data["TargetLanguage"] = "en"
    # form_data["SourceText"] = "Hello"
    # form_data["Scene"] = "general"
    # signature_request.body = bytes(form_data_to_string(form_data), 'utf-8')
    # signature_request.headers["content-type"] = "application/x-www-form-urlencoded"

    # # 例 4: ROA API POST リクエスト
    # http_method = "POST"
    # canonical_uri = "/clusters"
    # host = "cs.cn-beijing.aliyuncs.com"
    # x_acs_action = "CreateCluster"
    # x_acs_version = "2015-12-15"
    # signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
    # リクエストパラメーターはメタデータで "in":"body" と表示され、ボディ経由で渡されます。
    # body = OrderedDict()
    # body["name"] = "testDemo"
    # body["region_id"] = "cn-beijing"
    # body["cluster_type"] = "ExternalKubernetes"
    # body["vpcid"] = "vpc-2zeou1uod4ylaXXXXXXXX"
    # body["container_cidr"] = "172.16.1.0/20"
    # body["service_cidr"] = "10.2.0.0/24"
    # body["security_group_id"] = "sg-2ze1a0rlgeo7XXXXXXXX"
    # body["vswitch_ids"] = ["vsw-2zei30dhfldu8XXXXXXXX"]
    # signature_request.body = bytes(json.dumps(body, separators=(',', ':')), 'utf-8')
    # signature_request.headers["content-type"] = "application/json; charset=utf-8"

    # # 例 5: ROA API GET リクエスト
    # http_method = "GET"
    # # canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: percent_code({path_parameter})
    # cluster_id_encode = percent_code("ca72cfced86db497cab79aa28XXXXXXXX")
    # canonical_uri = f"/clusters/{cluster_id_encode}/resources"
    # host = "cs.cn-beijing.aliyuncs.com"
    # x_acs_action = "DescribeClusterResources"
    # x_acs_version = "2015-12-15"
    # signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
    # signature_request.query_param['with_addon_resources'] = True

    # # 例 6: ROA API DELETE リクエスト
    # http_method = "DELETE"
    # # canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: percent_code({path_parameter})
    # cluster_id_encode = percent_code("ca72cfced86db497cab79aa28XXXXXXXX")
    # canonical_uri = f"/clusters/{cluster_id_encode}"
    # host = "cs.cn-beijing.aliyuncs.com"
    # x_acs_action = "DeleteCluster"
    # x_acs_version = "2015-12-15"
    # signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)

    get_authorization(signature_request)
    call_api(signature_request)

Go の例

説明

サンプルコードは go1.22.2 で実行されます。特定の状況に応じてコードを調整する必要がある場合があります。

ターミナルで次のコマンドを実行します:

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         []byte
	queryParam   map[string]interface{}
}

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]interface{}),
	}
	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
}

// os.Getenv() は環境変数から AccessKey ID と AccessKey Secret を取得します。
var (
	AccessKeyId     = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
	AccessKeySecret = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
	SecurityToken   = os.Getenv("ALIBABA_CLOUD_SECURITY_TOKEN")
	ALGORITHM       = "ACS3-HMAC-SHA256"
)

// 署名の例。必要に応じて main メソッドの例のパラメーターを置き換えてください。
// canonicalUri 値を取得するロジックは、ROA API と RPC API の唯一の違いです。残りは同様です。
// API メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得し、パラメーターを SignatureRequest にカプセル化します。
// 1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、queryParam を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
// 2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/octet-stream または application/json です。RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
// 3. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/x-www-form-urlencoded です。
func main() {
	// RPC API リクエストの例 1: リクエストパラメーターは "query" にあります
	httpMethod := "POST"                   // リクエストメソッド。ほとんどの RPC API は POST と GET の両方をサポートしています。この例では POST を使用します。
	canonicalUri := "/"                    // RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
	host := "ecs.cn-hangzhou.aliyuncs.com" // クラウドプロダクトのエンドポイント
	xAcsAction := "DescribeInstanceStatus" // API 名
	xAcsVersion := "2014-05-26"            // API バージョン番号
	req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
	// DescribeInstanceStatus リクエストパラメーター:
	// RegionId はメタデータで String 型、"in":"query"、必須
	req.queryParam["RegionId"] = "cn-hangzhou"
	// InstanceId はメタデータで array 型、"in":"query"、必須ではない
	instanceIds := []interface{}{"i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"}
	req.queryParam["InstanceId"] = instanceIds

	// // RPC API リクエストの例 2: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ)
	// httpMethod := "POST"
	// canonicalUri := "/"
	// host := "ocr-api.cn-hangzhou.aliyuncs.com"
	// xAcsAction := "RecognizeGeneral"
	// xAcsVersion := "2021-07-07"
	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
	// // ファイルコンテンツを読み取る
	// filePath := "D:\\test.png"
	// bytes, err := os.ReadFile(filePath)
	// if err != nil {
	//     fmt.Println("Error reading file:", err)
	//     return
	// }
	// req.body = bytes
	// req.headers["content-type"] = "application/octet-stream"

	// // RPC API リクエストの例 3: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ)
	// httpMethod := "POST"
	// canonicalUri := "/"
	// host := "mt.aliyuncs.com"
	// xAcsAction := "TranslateGeneral"
	// xAcsVersion := "2018-10-12"
	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
	// // TranslateGeneral リクエストパラメーター:
	// // Context はメタデータで String 型、"in":"query"、必須ではない
	// req.queryParam["Context"] = "Morning"
	// // FormatType、SourceLanguage、TargetLanguage などのパラメーターはメタデータで "in":"formData" と表示されます
	// body := make(map[string]interface{})
	// body["FormatType"] = "text"
	// body["SourceLanguage"] = "zh"
	// body["TargetLanguage"] = "en"
	// body["SourceText"] = "Hello"
	// body["Scene"] = "general"
	// str := formDataToString(body)
	// req.body = []byte(*str)
	// req.headers["content-type"] = "application/x-www-form-urlencoded"

	// // ROA API POST リクエスト
	// httpMethod := "POST"
	// canonicalUri := "/clusters"
	// host := "cs.cn-beijing.aliyuncs.com"
	// xAcsAction := "CreateCluster"
	// xAcsVersion := "2015-12-15"
	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
	// // リクエストパラメーターをカプセル化します。リクエストパラメーターはメタデータで "in": "body" と表示され、パラメーターがボディにあることを意味します。
	// body := make(map[string]interface{})
	// body["name"] = "testDemo"
	// body["region_id"] = "cn-beijing"
	// body["cluster_type"] = "ExternalKubernetes"
	// body["vpcid"] = "vpc-2zeou1uod4ylaXXXXXXXX"
	// body["container_cidr"] = "10.0.0.0/8"
	// body["service_cidr"] = "172.16.1.0/20"
	// body["security_group_id"] = "sg-2ze1a0rlgeo7XXXXXXXX"
	// vswitch_ids := []interface{}{"vsw-2zei30dhfldu8XXXXXXXX"}
	// body["vswitch_ids"] = vswitch_ids
	// jsonBytes, err := json.Marshal(body)
	// if err != nil {
	//     fmt.Println("Error marshaling to JSON:", err)
	//     return
	// }
	// req.body = []byte(jsonBytes)
	// req.headers["content-type"] = "application/json; charset=utf-8"

	// // ROA API GET リクエスト
	// httpMethod := "GET"
	// // canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: percentCode({path_parameter})
	// canonicalUri := "/clusters/" + percentCode("c558c166928f9446dae400d106e124f66") + "/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"

	// // ROA API DELETE リクエスト
	// httpMethod := "DELETE"
	// // canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: percentCode({path_parameter})
	// canonicalUri := "/clusters/" + percentCode("c558c166928f9446dae400d106e124f66")
	// host := "cs.cn-beijing.aliyuncs.com"
	// xAcsAction := "DeleteCluster"
	// xAcsVersion := "2015-12-15"
	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)

	// 署名プロセス
	getAuthorization(req)
	// API を呼び出す
	error := callAPI(req)
	if error != nil {
		println(error.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, fmt.Sprintf("%v", v))
	}
	urlStr += "?" + q.Encode()
	fmt.Println(urlStr)

	httpReq, err := http.NewRequest(req.httpMethod, urlStr, strings.NewReader(string(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) {
	// queryParam の List および Map 型のパラメーターを処理し、パラメーターをフラット化する
	newQueryParams := make(map[string]interface{})
	processObject(newQueryParams, "", req.queryParam)
	req.queryParam = newQueryParams
	// ステップ 1: 正規リクエスト文字列を作成する
	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(fmt.Sprintf("%v", v))) + "&"
	}
	canonicalQueryString = strings.TrimSuffix(canonicalQueryString, "&")
	fmt.Printf("canonicalQueryString========>%s\n", canonicalQueryString)

	var bodyContent []byte
	if req.body == nil {
		bodyContent = []byte("")
	} else {
		bodyContent = req.body
	}
	hashedRequestPayload := sha256Hex(bodyContent)
	req.headers["x-acs-content-sha256"] = hashedRequestPayload

	if SecurityToken != "" {
		req.headers["x-acs-security-token"] = SecurityToken
	}

	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)

	// ステップ 2: 署名文字列を作成する
	hashedCanonicalRequest := sha256Hex([]byte(canonicalRequest))
	stringToSign := ALGORITHM + "\n" + hashedCanonicalRequest
	fmt.Printf("stringToSign========>\n%s\n", stringToSign)

	// ステップ 3: 署名を計算する
	byteData, err := hmac256([]byte(AccessKeySecret), stringToSign)
	if err != nil {
		fmt.Println(err)
		panic(err)
	}
	signature := strings.ToLower(hex.EncodeToString(byteData))

	// ステップ 4: Authorization ヘッダーを構築する
	authorization := ALGORITHM + " Credential=" + AccessKeyId + ",SignedHeaders=" + signedHeaders + ",Signature=" + signature
	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(byteArray []byte) string {
	// SHA-256 ハッシュ関数をインスタンス化する
	hash := sha256.New()
	// 文字列をハッシュ関数に書き込む
	_, _ = hash.Write(byteArray)
	// SHA-256 ハッシュ値を計算し、小文字の 16 進数文字列に変換する
	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
}

func formDataToString(formData map[string]interface{}) *string {
	tmp := make(map[string]interface{})
	processObject(tmp, "", formData)
	res := ""
	urlEncoder := url.Values{}
	for key, value := range tmp {
		v := fmt.Sprintf("%v", value)
		urlEncoder.Add(key, v)
	}
	res = urlEncoder.Encode()
	return &res
}

// processObject はオブジェクトを再帰的に処理し、複雑なオブジェクト (Map や List など) をフラットなキーと値のペアに展開します
func processObject(mapResult map[string]interface{}, key string, value interface{}) {
	if value == nil {
		return
	}

	switch v := value.(type) {
	case []interface{}:
		for i, item := range v {
			processObject(mapResult, fmt.Sprintf("%s.%d", key, i+1), item)
		}
	case map[string]interface{}:
		for subKey, subValue := range v {
			processObject(mapResult, fmt.Sprintf("%s.%s", key, subKey), subValue)
		}
	default:
		if strings.HasPrefix(key, ".") {
			key = key[1:]
		}
		if b, ok := v.([]byte); ok {
			mapResult[key] = string(b)
		} else {
			mapResult[key] = fmt.Sprintf("%v", v)
		}
	}
}

Node.js の例

説明

サンプルコードは Node.js v20.13.1 で実行されます。特定の状況に応じてコードを調整する必要がある場合があります。

この例では Node.js を使用します。

const crypto = require('crypto');
const fs = require('fs');

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.initHeader();
    }

    initHeader() {
        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;
const securityToken = process.env.ALIBABA_CLOUD_SECURITY_TOKEN;
const encoder = new TextEncoder()

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 {
        newQueryParam = {};
        processObject(newQueryParam, "", signRequest.queryParam);
        signRequest.queryParam = newQueryParam;
        // ステップ 1: 正規リクエスト文字列を作成する
        const canonicalQueryString = Object.entries(signRequest.queryParam)
            .sort(([a], [b]) => a.localeCompare(b))
            .map(([key, value]) => `${percentCode(key)}=${percentCode(value)}`)
            .join('&');

        // リクエストボディ。リクエストボディが空の場合 (GET リクエストなど)、RequestPayload は固定の空文字列です。
        const requestPayload = signRequest.body || encoder.encode('');
        const hashedRequestPayload = sha256Hex(requestPayload);
        signRequest.headers['x-acs-content-sha256'] = hashedRequestPayload;
        if (securityToken) {
            signRequest.headers['x-acs-security-token'] = securityToken;
        }

        // すべてのキーを小文字に変換する
        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.map(key => `${key}:${signRequest.headers[key]}`).join('\n') + '\n';

        const canonicalRequest = [
            signRequest.httpMethod,
            signRequest.canonicalUri,
            canonicalQueryString,
            canonicalHeaders,
            signedHeaders,
            hashedRequestPayload
        ].join('\n');
        console.log('canonicalRequest=========>\n', canonicalRequest);

        // ステップ 2: 署名文字列を作成する
        const hashedCanonicalRequest = sha256Hex(encoder.encode(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) {
            const query = new URLSearchParams(signRequest.queryParam);
            url += '?' + query.toString();
        }
        console.log('url=========>', url);

        // リクエストオプションを設定する
        let options = {
            method: signRequest.httpMethod.toUpperCase(),
            headers: signRequest.headers
        };

        // リクエストボディを処理する
        if (signRequest.body && ['POST', 'PUT'].includes(signRequest.httpMethod.toUpperCase())) {
            options.body = signRequest.body;
        }
        return (await fetch(url, options)).text();
    } catch (error) {
        console.error('Failed to send request:', 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', key);
    hmac.update(data, 'utf8');
    return hmac.digest('hex').toLowerCase();
}

function sha256Hex(bytes) {
    const hash = crypto.createHash('sha256');
    const digest = hash.update(bytes).digest('hex');
    return digest.toLowerCase();
}

function formDataToString(formData) {
    const tmp = {};
    processObject(tmp, "", formData);
    let queryString = '';
    for (let [key, value] of Object.entries(tmp)) {
        if (queryString !== '') {
            queryString += '&';
        }
        queryString += encodeURIComponent(key) + '=' + encodeURIComponent(value);
    }
    return queryString;
}

function processObject(map, key, value) {
    // 値が null の場合、それ以上の処理は不要です
    if (value === null) {
        return;
    }
    if (key === null) {
        key = "";
    }

    // 値が配列の場合、各要素を反復処理し、再帰的に処理します
    if (Array.isArray(value)) {
        value.forEach((item, index) => {
            processObject(map, `${key}.${index + 1}`, item);
        });
    } else if (typeof value === 'object' && value !== null) {
        // 値がオブジェクトの場合、各キーと値のペアを反復処理し、再帰的に処理します
        Object.entries(value).forEach(([subKey, subValue]) => {
            processObject(map, `${key}.${subKey}`, subValue);
        });
    } else {
        // キーが "." で始まる場合、キーの連続性を維持するために先頭の "." を削除します
        if (key.startsWith('.')) {
            key = key.slice(1);
        }
        map[key] = String(value);
    }
}

/**
 * 署名の例。必要に応じて main メソッドの例のパラメーターを置き換えてください。
 * canonicalUri 値を取得するロジックは、ROA API と RPC API の唯一の違いです。残りは同様です。
 *
 * API メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得し、パラメーターを SignatureRequest にカプセル化します。
 * 1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、queryParam を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
 * 2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/octet-stream または application/json です。RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
 * 3. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/x-www-form-urlencoded です。
 */

// RPC API リクエストの例 1: リクエストパラメーターは "query" にあります
const httpMethod = 'POST'; // リクエストメソッド。ほとんどの RPC API は POST と GET の両方をサポートしています。この例では POST を使用します。
const canonicalUri = '/'; // RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
const host = 'ecs.cn-hangzhou.aliyuncs.com'; // エンドポイント
const xAcsAction = 'DescribeInstanceStatus'; // API 名
const xAcsVersion = '2014-05-26'; // API バージョン番号
const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// DescribeInstanceStatus リクエストパラメーター:
signRequest.queryParam = {
    // RegionId はメタデータで String 型、"in":"query"、必須
    RegionId: 'cn-hangzhou',
    // InstanceId はメタデータで array 型、"in":"query"、必須ではない
    InstanceId: ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"],
}


// // RPC API リクエストの例 2: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ)
// const httpMethod = 'POST';
// const canonicalUri = '/';
// const host = 'ocr-api.cn-hangzhou.aliyuncs.com';
// const xAcsAction = 'RecognizeGeneral';
// const xAcsVersion = '2021-07-07';
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// const filePath = 'D:\\test.png';
// const bytes = fs.readFileSync(filePath);
// // リクエストパラメーターはメタデータで "in": "body" と表示され、パラメーターがボディにあることを意味します。
// signRequest.body = bytes;
// signRequest.headers['content-type'] = 'application/octet-stream';


// // RPC API リクエストの例 3: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ)
// const httpMethod = 'POST'; // リクエストメソッド。ほとんどの RPC API は POST と GET の両方をサポートしています。この例では POST を使用します。
// const canonicalUri = '/'; // RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
// const host = 'mt.aliyuncs.com'; // エンドポイント
// const xAcsAction = 'TranslateGeneral'; // API 名
// const xAcsVersion = '2018-10-12'; // API バージョン番号
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// // TranslateGeneral リクエストパラメーター:
// // Context はメタデータで String 型、"in":"query"、必須ではない
// signRequest.queryParam["Context"] = "Morning";
// // FormatType、SourceLanguage、TargetLanguage などのパラメーターはメタデータで "in":"formData" と表示されます
// const formData = {
//     SourceLanguage: "zh",
//     TargetLanguage: "en",
//     FormatType: "text",
//     Scene: "general",
//     SourceText: 'Hello'
// }
// const str = formDataToString(formData)
// signRequest.body = encoder.encode(str);
// signRequest.headers['content-type'] = 'application/x-www-form-urlencoded';


// // ROA API 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);
// // リクエストパラメーターはメタデータで "in": "body" と表示され、パラメーターがボディにあることを意味します。
// const body = {
//     name: 'testDemo',
//     region_id: 'cn-beijing',
//     cluster_type: 'ExternalKubernetes',
//     vpcid: 'vpc-2zeou1uod4ylaf35teei9',
//     container_cidr: '10.0.0.0/8',
//     service_cidr: '172.16.3.0/20',
//     security_group_id: 'sg-2ze1a0rlgeo7dj37dd1q',
//     vswitch_ids: [
//         'vsw-2zei30dhfldu8ytmtarro'
//       ],
// }
// signRequest.body = encoder.encode(JSON.stringify(body));
// signRequest.headers['content-type'] = 'application/json';


// // ROA API GET リクエスト
// const httpMethod = 'GET';
// // canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: percentCode({path_parameter})
// const canonicalUri = '/clusters/' + percentCode("c28c2615f8bfd466b9ef9a76c61706e96") + '/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,
// }


// // ROA API DELETE リクエスト
// const httpMethod = 'DELETE';
// // canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: percentCode({path_parameter})
// const canonicalUri = '/clusters/' + percentCode("c28c2615f8bfd466b9ef9a76c61706e96");
// const host = 'cs.cn-beijing.aliyuncs.com';
// const xAcsAction = 'DeleteCluster';
// const xAcsVersion = '2015-12-15';
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);

getAuthorization(signRequest);
// API を呼び出す
callApi(signRequest).then(r => {
    console.log(r);
}).catch(error => {
    console.error(error);
});

PHP の例

説明

サンプルコードは PHP 7.4.33 で実行されます。特定の状況に応じてコードを調整する必要がある場合があります。

<?php

class SignatureDemo
{
    // 暗号化アルゴリズム
    private $ALGORITHM;
    // Access Key ID
    private $AccessKeyId;
    // Access Key Secret
    private $AccessKeySecret;

    private $SecurityToken;

    public function __construct()
    {
        date_default_timezone_set('UTC'); // タイムゾーンを GMT に設定
        $this->AccessKeyId = getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'); // getenv() は環境変数から RAM ユーザーの Access Key ID を取得します
        $this->AccessKeySecret = getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'); // getenv() は環境変数から RAM ユーザーの Access Key Secret を取得します
        $this->SecurityToken = getenv('ALIBABA_CLOUD_SECURITY_TOKEN');
        $this->ALGORITHM = 'ACS3-HMAC-SHA256'; // 暗号化アルゴリズムを設定
    }

    /**
     * 署名の例。必要に応じて main メソッドの例のパラメーターを置き換えてください。
     * canonicalUri 値を取得するロジックは、ROA API と RPC API の唯一の違いです。残りは同様です。
     *
     * API メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得し、パラメーターを SignatureRequest にカプセル化します。
     * 1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、queryParam を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
     * 2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/octet-stream または application/json です。RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
     * 3. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/x-www-form-urlencoded です。
     */
    public function main()
    {
        // RPC API リクエストの例 1: リクエストパラメーターは "query" にあります
        $request = $this->createRequest('POST', '/', 'ecs.cn-hangzhou.aliyuncs.com', 'DescribeInstanceStatus', '2014-05-26');
        // DescribeInstanceStatus リクエストパラメーター:
        $request['queryParam'] = [
            // RegionId はメタデータで String 型、"in":"query"、必須
            'RegionId' => 'cn-hangzhou',
            // InstanceId はメタデータで array 型、"in":"query"、必須ではない
            'InstanceId' => ["i-bp11ht4h2kdXXXXXXXX", "i-bp16maz3h3xgXXXXXXXX", "i-bp10r67hmslXXXXXXXX"]
        ];

        // // RPC API リクエストの例 2: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ)
        // $request = $this->createRequest('POST', '/', 'ocr-api.cn-hangzhou.aliyuncs.com', 'RecognizeGeneral', '2021-07-07');
        // // リクエストパラメーターはメタデータで "in": "body" と表示され、ボディ経由で渡されます。
        // $filePath = 'D:\\test.png';
        // // バイナリファイルを渡すためにファイルリソースを使用する
        // $fileResource = fopen($filePath, 'rb');
        // $request['body'] = stream_get_contents($fileResource); 
        // $request['headers']['content-type'] = 'application/octet-stream'; // Content-Type を application/octet-stream に設定
        // // ファイルリソースを閉じる
        // fclose($fileResource);


        // // RPC API リクエストの例 3: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ)
        // $request = $this->createRequest('POST', '/', 'mt.aliyuncs.com', 'TranslateGeneral', '2018-10-12');
        // // TranslateGeneral リクエストパラメーター:
        // $request['queryParam'] = [
        //     // Context はメタデータで String 型、"in":"query"、必須ではない
        //     'Context' => 'Morning',
        // ];
        // $formData = [
        //     'FormatType' => 'text',
        //     'SourceLanguage' => 'zh',
        //     'TargetLanguage' => 'en',
        //     'SourceText' => 'Hello',
        //     'Scene' => 'general',
        // ];
        // $str = self::formDataToString($formData);
        // $request['body'] = $str;
        // $request['headers']['content-type'] = 'application/x-www-form-urlencoded';


        // // ROA API POST リクエスト
        // $request = $this->createRequest('POST', '/clusters', 'cs.cn-beijing.aliyuncs.com', 'CreateCluster', '2015-12-15');
        // $bodyData = [
        //     'name' => 'Test Cluster',
        //     'region_id' => 'cn-beijing',
        //     'cluster_type' => 'ExternalKubernetes',
        //     'vpcid' => 'vpc-2zeou1uod4ylaXXXXXXXX',
        //     'service_cidr' => '10.2.0.0/24',
        //     'security_group_id' => 'sg-2ze1a0rlgeo7XXXXXXXX',
        //     "vswitch_ids" => [
        //         "vsw-2zei30dhfldu8XXXXXXXX"
        //     ]
        // ];
        // $request['body'] = json_encode($bodyData, JSON_UNESCAPED_UNICODE);
        // $request['headers']['content-type'] = 'application/json; charset=utf-8'; 


        // // ROA API GET リクエスト
        // // canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: rawurlencode({path_parameter})
        // $cluster_id = 'c930976b3b1fc4e02bc09831dXXXXXXXX';
        // $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,
        // ];


        // // ROA API DELETE リクエスト
        // $cluster_id = 'c930976b3b1fc4e02bc09831dXXXXXXXX';
        // $canonicalUri = sprintf("/clusters/%s", rawurlencode($cluster_id));
        // $request = $this->createRequest('DELETE', $canonicalUri, 'cs.cn-beijing.aliyuncs.com', 'DeleteCluster', '2015-12-15');

        $this->getAuthorization($request);
        // API を呼び出す
        $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 getAuthorization(&$request)
    {
        $request['queryParam'] = $this->processObject($request['queryParam']);
        $canonicalQueryString = $this->buildCanonicalQueryString($request['queryParam']);
        $hashedRequestPayload = hash('sha256', $request['body'] ?? '');
        $request['headers']['x-acs-content-sha256'] = $hashedRequestPayload;

        if($this->SecurityToken){
            $request['headers']['x-acs-security-token'] = $this->SecurityToken;
        }

        $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'];

            // URL にリクエストパラメーターを追加する
            if (!empty($request['queryParam'])) {
                $url .= '?' . http_build_query($request['queryParam']);
            }

            echo $url;
            // cURL セッションを初期化する
            $ch = curl_init();

            // 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'])); // リクエストヘッダーを追加する

            // リクエストタイプに基づいて 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;
                default:
                    echo "Unsupported HTTP method: " . $request['body'];
                    throw new Exception("Unsupported HTTP method");
            }

            // リクエストを送信する
            $result = curl_exec($ch);

            // エラーをチェックする
            if (curl_errno($ch)) {
                echo "Failed to send request: " . curl_error($ch);
            } else {
                echo $result;
            }

        } catch (Exception $e) {
            echo "Error: " . $e->getMessage();
        } finally {
            // cURL セッションを閉じる
            curl_close($ch);
        }
    }

    function formDataToString($formData)
    {
        $res = self::processObject($formData);
        return http_build_query($res);
    }

    function processObject($value)
    {
        // 値が null の場合、それ以上の処理は不要です
        if ($value === null) {
            return;
        }
        $tmp = [];
        foreach ($value as $k => $v) {
            if (0 !== strpos($k, '_')) {
                $tmp[$k] = $v;
            }
        }
        return self::flatten($tmp);
    }

    private static function flatten($items = [], $delimiter = '.', $prepend = '')
    {
        $flatten = [];
        foreach ($items as $key => $value) {
            $pos = \is_int($key) ? $key + 1 : $key;

            if (\is_object($value)) {
                $value = get_object_vars($value);
            }

            if (\is_array($value) && !empty($value)) {
                $flatten = array_merge(
                    $flatten,
                    self::flatten($value, $delimiter, $prepend . $pos . $delimiter)
                );
            } else {
                if (\is_bool($value)) {
                    $value = true === $value ? 'true' : 'false';
                }
                $flatten["$prepend$pos"] = $value;
            }
        }
        return $flatten;
    }


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


    private function buildCanonicalQueryString($queryParams)
    {

        ksort($queryParams);
        // クエリパラメーターを構築してエンコードする
        $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)
    {
        // ヘッダーをキーでソートし、連結する
        uksort($headers, 'strcasecmp');
        $canonicalHeaders = '';
        foreach ($headers as $key => $value) {
            $canonicalHeaders .= strtolower($key) . ':' . trim($value) . "\n";
        }
        return $canonicalHeaders;
    }

    private function buildSignedHeaders($headers)
    {
        // 署名付きヘッダー文字列を構築する
        $signedHeaders = array_keys($headers);
        sort($signedHeaders, SORT_STRING | SORT_FLAG_CASE);
        return implode(';', array_map('strtolower', $signedHeaders));
    }
}

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

.NET の例

説明

サンプルコードは .NET 8.0.302 で実行されます。特定の状況に応じてコードを調整する必要がある場合があります。

using System.Globalization;
using System.Net;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using Newtonsoft.Json;

namespace SignatureDemo
{
    public class Request
    {
        public string HttpMethod { get; private set; }
        public string CanonicalUri { get; private set; }
        public string Host { get; private set; }
        public string XAcsAction { get; private set; }
        public string XAcsVersion { get; private set; }
        public SortedDictionary<string, object> Headers { get; private set; }
        public byte[]? Body { get; set; }
        public Dictionary<string, object> QueryParam { get; set; }

        public Request(string httpMethod, string canonicalUri, string host, string xAcsAction, string xAcsVersion)
        {
            HttpMethod = httpMethod;
            CanonicalUri = canonicalUri;
            Host = host;
            XAcsAction = xAcsAction;
            XAcsVersion = xAcsVersion;
            Headers = [];
            QueryParam = [];
            Body = null;
            InitHeader();
        }

        private void InitHeader()
        {
            Headers["host"] = Host;
            Headers["x-acs-action"] = XAcsAction;
            Headers["x-acs-version"] = XAcsVersion;
            DateTime utcNow = DateTime.UtcNow;
            Headers["x-acs-date"] = utcNow.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.InvariantCulture);
            Headers["x-acs-signature-nonce"] = Guid.NewGuid().ToString();
        }
    }

    public class Program
    {
        private static readonly string AccessKeyId = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID") ?? throw new InvalidOperationException("The ALIBABA_CLOUD_ACCESS_KEY_ID environment variable is not set");
        private static readonly string AccessKeySecret = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET") ?? throw new InvalidOperationException("The ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable is not set");
        private static readonly string? SecurityToken = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_SECURITY_TOKEN");
        private const string Algorithm = "ACS3-HMAC-SHA256";
        private const string ContentType = "content-type";

        /**
        * 署名の例。必要に応じて main メソッドの例のパラメーターを置き換えてください。
        * canonicalUri 値を取得するロジックは、ROA API と RPC API の唯一の違いです。残りは同様です。
        *
        * API メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得し、パラメーターを SignatureRequest にカプセル化します。
        * 1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、queryParam を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
        * 2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/octet-stream または application/json です。RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
        * 3. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/x-www-form-urlencoded です。
        */
        public static void Main(string[] args)
        {
            // RPC API リクエストの例 1: リクエストパラメーターは "query" にあります
            string httpMethod = "POST"; // リクエストメソッド。ほとんどの RPC API は POST と GET の両方をサポートしています。この例では POST を使用します。
            string canonicalUri = "/"; // RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
            string host = "ecs.cn-hangzhou.aliyuncs.com"; // クラウドプロダクトのエンドポイント
            string xAcsAction = "DescribeInstanceStatus"; // API 名
            string xAcsVersion = "2014-05-26"; // API バージョン番号
            var request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
            // DescribeInstanceStatus リクエストパラメーター:
            // RegionId はメタデータで String 型、"in":"query"、必須
            request.QueryParam["RegionId"] = "cn-hangzhou"; 
            // InstanceId はメタデータで array 型、"in":"query"、必須ではない
            List<string> instanceIds = ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"];
            request.QueryParam["InstanceId"] = instanceIds; 

            // // RPC API リクエストの例 2: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ)
            // string httpMethod = "POST"; 
            // string canonicalUri = "/"; 
            // string host = "ocr-api.cn-hangzhou.aliyuncs.com"; 
            // string xAcsAction = "RecognizeGeneral"; 
            // string xAcsVersion = "2021-07-07"; 
            // var request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
            // // リクエストパラメーターはメタデータで "in": "body" と表示され、ボディ経由で渡されます。
            // request.Body = File.ReadAllBytes(@"D:\test.png");
            // request.Headers["content-type"] = "application/octet-stream";


            // // RPC API リクエストの例 3: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ)
            // string httpMethod = "POST"; 
            // string canonicalUri = "/"; 
            // string host = "mt.aliyuncs.com"; 
            // string xAcsAction = "TranslateGeneral"; 
            // string xAcsVersion = "2018-10-12"; 
            // var request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
            // // TranslateGeneral リクエストパラメーター:
            // // Context はメタデータで String 型、"in":"query"、必須ではない
            // request.QueryParam["Context"] = "Morning"; 
            // // FormatType、SourceLanguage、TargetLanguage などのパラメーターはメタデータで "in":"formData" と表示されます
            // var body = new Dictionary<string, object>
            // {
            //     { "FormatType", "text" },
            //     { "SourceLanguage", "zh" },
            //     { "TargetLanguage", "en" },
            //     { "SourceText", "Hello" },
            //     { "Scene", "general" },
            // };
            // var str = FormDataToString(body);
            // request.Body = Encoding.UTF8.GetBytes(str);
            // request.Headers[ContentType] = "application/x-www-form-urlencoded";


            // // ROA API POST リクエスト
            // String httpMethod = "POST";
            // String canonicalUri = "/clusters";
            // String host = "cs.cn-beijing.aliyuncs.com";
            // String xAcsAction = "CreateCluster"; 
            // String xAcsVersion = "2015-12-15"; 
            // Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
            // // リクエストボディ。JsonConvert を使用してボディを JSON 文字列に変換します。
            // var body = new SortedDictionary<string, object>
            // {
            //     { "name", "testDemo" },
            //     { "region_id", "cn-beijing" },
            //     { "cluster_type", "ExternalKubernetes" },
            //     { "vpcid", "vpc-2zeou1uod4ylaXXXXXXXX" },
            //     { "container_cidr", "10.0.0.0/8" },
            //     { "service_cidr", "172.16.1.0/20" },
            //     { "security_group_id", "sg-2ze1a0rlgeo7XXXXXXXX" },
            //     { "vswitch_ids", new List<string>{"vsw-2zei30dhfldu8XXXXXXXX"} },
            // };
            // string jsonBody = JsonConvert.SerializeObject(body, Formatting.None);
            // request.Body = Encoding.UTF8.GetBytes(jsonBody);
            // request.Headers[ContentType] = "application/json; charset=utf-8";

            // // ROA API GET リクエスト
            // String httpMethod = "GET";
            // // canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: PercentCode({path_parameter})
            // String canonicalUri = "/clusters/" + PercentCode("c81d501a467594eab873edbf2XXXXXXXX") + "/resources";
            // String host = "cs.cn-beijing.aliyuncs.com"; 
            // String xAcsAction = "DescribeClusterResources"; 
            // String xAcsVersion = "2015-12-15"; 
            // Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
            // request.QueryParam["with_addon_resources"]=true;

            // // ROA API DELETE リクエスト
            // String httpMethod = "DELETE";
            // // canonicalUri にパスパラメーターがある場合、パスパラメーターをエンコードします: PercentCode({path_parameter})
            // String canonicalUri = "/clusters/" + PercentCode("c81d501a467594eab873edbf2XXXXXXXX");
            // String host = "cs.cn-beijing.aliyuncs.com"; 
            // String xAcsAction = "DeleteCluster"; 
            // String xAcsVersion = "2015-12-15"; 
            // Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);

            GetAuthorization(request);
            // API を呼び出す
            var result = CallApiAsync(request);
            Console.WriteLine($"result:{result.Result}");
        }

        private static async Task<string?> CallApiAsync(Request request)
        {
            try
            {
                // httpClient を宣言する
                using var httpClient = new HttpClient();

                // URL を構築する
                string url = $"https://{request.Host}{request.CanonicalUri}";
                var uriBuilder = new UriBuilder(url);
                var query = new List<string>();

                // リクエストパラメーターを追加する
                foreach (var entry in request.QueryParam.OrderBy(e => e.Key.ToLower()))
                {
                    string value = entry.Value?.ToString() ?? "";
                    query.Add($"{entry.Key}={Uri.EscapeDataString(value)}");
                }

                uriBuilder.Query = string.Join("&", query);
                Console.WriteLine(uriBuilder.Uri);
                var requestMessage = new HttpRequestMessage
                {
                    Method = new HttpMethod(request.HttpMethod),
                    RequestUri = uriBuilder.Uri,
                };

                // リクエストヘッダーを設定する
                foreach (var entry in request.Headers)
                {
                    if (entry.Key == "Authorization")
                    {
                        requestMessage.Headers.TryAddWithoutValidation("Authorization", entry.Value.ToString()); ;
                    }
                    else if (entry.Key == ContentType) // main の定義と一致している必要があります
                    {
                        continue;
                    }
                    else
                    {
                        requestMessage.Headers.Add(entry.Key, entry.Value.ToString());
                    }
                }

                if (request.Body != null)
                {
                    HttpContent content = new ByteArrayContent(request.Body);
                    string contentType = request.Headers["content-type"].ToString();
                    content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
                    requestMessage.Content = content;
                }
                
                // リクエストを送信する
                HttpResponseMessage response = await httpClient.SendAsync(requestMessage);
                // 応答コンテンツを読み取る
                string result = await response.Content.ReadAsStringAsync();
                return result;
            }
            catch (UriFormatException e)
            {
                Console.WriteLine("Invalid URI syntax");
                Console.WriteLine(e.Message);
                return null;
            }
            catch (Exception e)
            {
                Console.WriteLine("Failed to send request");
                Console.WriteLine(e);
                return null;
            }
        }

        private static void GetAuthorization(Request request)
        {
            try
            {
                // queryParam の List および Map 型のパラメーターを処理し、パラメーターをフラット化する
                request.QueryParam = FlattenDictionary(request.QueryParam);

                // ステップ 1: 正規リクエスト文字列を作成する
                StringBuilder canonicalQueryString = new();
                foreach (var entry in request.QueryParam.OrderBy(e => e.Key.ToLower()))
                {
                    if (canonicalQueryString.Length > 0)
                    {
                        canonicalQueryString.Append('&');
                    }
                    canonicalQueryString.Append($"{PercentCode(entry.Key)}={PercentCode(entry.Value?.ToString() ?? "")}");
                }

                byte[] requestPayload = request.Body ?? Encoding.UTF8.GetBytes("");
                string hashedRequestPayload = Sha256Hash(requestPayload);
                request.Headers["x-acs-content-sha256"] = hashedRequestPayload;
                if (!string.IsNullOrEmpty(SecurityToken))
                {
                    request.Headers["x-acs-security-token"] = SecurityToken;
                }

                StringBuilder canonicalHeaders = new();
                StringBuilder signedHeadersSb = new();
                foreach (var entry in request.Headers.OrderBy(e => e.Key.ToLower()))
                {
                    if (entry.Key.StartsWith("x-acs-", StringComparison.CurrentCultureIgnoreCase) || entry.Key.Equals("host", StringComparison.OrdinalIgnoreCase) || entry.Key.Equals(ContentType, StringComparison.OrdinalIgnoreCase))
                    {
                        string lowerKey = entry.Key.ToLower();
                        string value = (entry.Value?.ToString() ?? "").Trim();
                        canonicalHeaders.Append($"{lowerKey}:{value}\n");
                        signedHeadersSb.Append($"{lowerKey};");
                    }
                }
                string signedHeaders = signedHeadersSb.ToString().TrimEnd(';');
                string canonicalRequest = $"{request.HttpMethod}\n{request.CanonicalUri}\n{canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\n{hashedRequestPayload}";
                Console.WriteLine($"canonicalRequest:{canonicalRequest}");

                // ステップ 2: 署名文字列を作成する
                string hashedCanonicalRequest = Sha256Hash(Encoding.UTF8.GetBytes(canonicalRequest));
                string stringToSign = $"{Algorithm}\n{hashedCanonicalRequest}";
                Console.WriteLine($"stringToSign:{stringToSign}");

                // ステップ 3: 署名を計算する
                string signature = HmacSha256(AccessKeySecret, stringToSign);

                // ステップ 4: Authorization ヘッダーを構築する
                string authorization = $"{Algorithm} Credential={AccessKeyId},SignedHeaders={signedHeaders},Signature={signature}";
                request.Headers["Authorization"] = authorization;
                Console.WriteLine($"authorization:{authorization}");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed to get authorization");
                Console.WriteLine(ex.Message);
            }
        }

        private static string FormDataToString(Dictionary<string, object> formData)
        {
            Dictionary<string, object> tileMap = FlattenDictionary( formData);
            
            StringBuilder result = new StringBuilder();
            bool first = true;
            string symbol = "&";

            foreach (var entry in tileMap)
            {
                string value = entry.Value?.ToString() ?? "";
                if (!string.IsNullOrEmpty(value))
                {
                    if (!first)
                    {
                        result.Append(symbol);
                    }
                    first = false;
                    result.Append(PercentCode(entry.Key));
                    result.Append("=");
                    result.Append(PercentCode(value));
                }
            }
            return result.ToString();
        }

        private static Dictionary<string, object> FlattenDictionary(Dictionary<string, object> dictionary, string prefix = "")
        {
            var result = new Dictionary<string, object>();
            foreach (var kvp in dictionary)
            {
                string key = string.IsNullOrEmpty(prefix) ? kvp.Key : $"{prefix}.{kvp.Key}";

                if (kvp.Value is Dictionary<string, object> nestedDict)
                {
                    var nestedResult = FlattenDictionary(nestedDict, key);
                    foreach (var nestedKvp in nestedResult)
                    {
                        result[nestedKvp.Key] = nestedKvp.Value;
                    }
                }
                else if (kvp.Value is List<string> list)
                {
                    for (int i = 0; i < list.Count; i++)
                    {
                        result[$"{key}.{i + 1}"] = list[i];
                    }
                }
                else
                {
                    result[key] = kvp.Value;
                }
            }
            return result;
        }

        private static string HmacSha256(string key, string message)
        {
            using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
            {
                byte[] hashMessage = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
                return BitConverter.ToString(hashMessage).Replace("-", "").ToLower();
            }
        }

        private static string Sha256Hash(byte[] input)
        {
            byte[] hashBytes = SHA256.HashData(input);
            return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
        }

        private static string PercentCode(string str)
        {
            if (string.IsNullOrEmpty(str))
            {
                throw new ArgumentException("Input string cannot be null or empty");
            }
            return Uri.EscapeDataString(str).Replace("+", "%20").Replace("*", "%2A").Replace("%7E", "~");
        }
    }
}

Rust の例

説明

サンプルコードは rustc 1.82.0 で実行されます。特定の状況に応じてコードを調整する必要がある場合があります。

Rust の例を実行するには、Cargo.toml ファイルに次の依存関係を追加します。

[dependencies]
serde = { version = "1.0" }
serde_json = "1.0"
rand = "0.8"
base64 = "0.21"
sha2 = "0.10"
chrono = "0.4"
hmac = "0.12"
hex = "0.4"
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
percent-encoding = "2.1"
use core::str;
use std::collections::{BTreeMap, HashMap};
use std::env;
use std::time::{SystemTime, SystemTimeError};
use chrono::DateTime;
use hmac::{Hmac, Mac};
use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
use rand::Rng;
use serde_json::{json, Value}; 
use std::borrow::Cow;    
use reqwest::{
    Client,
    header::{HeaderMap, HeaderValue}, Method, Response, StatusCode,
};
use sha2::{Digest, Sha256};
use base64::engine::general_purpose::STANDARD;
use base64::Engine;


// x-acs-date を生成
pub fn current_timestamp() -> Result<u64, SystemTimeError> {
    Ok(SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)?
        .as_secs())
}
// URL エンコーディング
pub fn percent_code(encode_str: &str) -> Cow<'_, str> {
    let encoded = utf8_percent_encode(encode_str, NON_ALPHANUMERIC)
        .to_string()
        .replace("+", "20%")
        .replace("%5F", "_")
        .replace("%2D", "-")
        .replace("%2E", ".")
        .replace("%7E", "~");
        
    Cow::Owned(encoded) // String または &str を保持できる Cow<str> を返す
}

fn flatten_target_ops(
    targets: Vec<HashMap<&str, &str>>,
    base_key: &str,
) -> Vec<(&'static str, &'static str)> {
    let mut result = Vec::new();

    for (idx, item) in targets.iter().enumerate() {
        let prefix = format!("{}.{}", base_key, idx + 1);

        for (&k, &v) in item {
            let key = format!("{}.{}", prefix, k);
            let key_static: &'static str = Box::leak(key.into_boxed_str());
            let value_static: &'static str = Box::leak(v.to_string().into_boxed_str());

            result.push((key_static, value_static));
        }
    }

    result
}

/// SHA-256 ハッシュを計算
pub fn sha256_hex(message: &str) -> String {
    let mut hasher = Sha256::new();
    hasher.update(message);
    format!("{:x}", hasher.finalize()).to_lowercase()
}
// HMAC-SHA256
pub fn hmac256(key: &[u8], message: &str) -> Result<Vec<u8>, String> {
    let mut mac = Hmac::<Sha256>::new_from_slice(key)
        .map_err(|e| format!("use data key on sha256 fail:{}", e))?;
    mac.update(message.as_bytes());
    let signature = mac.finalize();
    Ok(signature.into_bytes().to_vec())
}
// 署名用の一意の乱数を生成
pub fn generate_random_string(length: usize) -> String {
    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
    let mut rng = rand::thread_rng();
    (0..length)
        .map(|_| CHARSET[rng.gen_range(0..CHARSET.len())] as char)
        .collect()
}
pub fn generate_nonce() -> String {
    generate_random_string(32)
}
// 正規クエリ文字列を構築 (エンコード済み)
pub fn build_sored_encoded_query_string(query_params: &[(&str, &str)]) -> String {
    let sorted_query_params: BTreeMap<_, _> = query_params.iter().copied().collect();
    let encoded_params: Vec<String> = sorted_query_params
        .into_iter()
        .map(|(k, v)| {
            let encoded_key = percent_code(k);
            let encoded_value = percent_code(v);
            format!("{}={}", encoded_key, encoded_value)
        })
        .collect();
    encoded_params.join("&")
}
// 応答を読み取る
pub async fn read_response(result: Response) -> Result<(StatusCode, String), String> {
    let status = result.status();
    let data = result.bytes().await.map_err(|e| format!("Read response body failed: {}", e))?;
    let res = match str::from_utf8(&data) {
        Ok(s) => s.to_string(),
        Err(_) => return Err("Body contains non UTF-8 characters".to_string()),
    };
    Ok((status, res))
}
// FormData の値の型を定義
#[derive(Debug, Clone)]
pub enum FormValue {
    String(String),
    Vec(Vec<String>),
    HashMap(HashMap<String, String>),
}
// Json、Binary、FormData を含むリクエストボディの型を統一的に扱うためのリクエストボディ列挙型を定義
pub enum RequestBody {
    Json(HashMap<String, Value>), // Json
    Binary(Vec<u8>), // バイナリ
    FormData(HashMap<String, FormValue>), //  FormData 
    None,
}
// 正規リクエスト
pub async fn call_api(
    client: Client,
    method: Method,
    host: &str,
    canonical_uri: &str,
    query_params: &[(&str, &str)], 
    action: &str,
    version: &str,
    body: RequestBody,   
    access_key_id: &str,
    access_key_secret: &str,
) -> Result<String, String> {

    // ボディの型に基づいてリクエストボディのコンテンツを処理し、処理されたコンテンツを body_content 変数に保存する
    let body_content = match &body { 
        RequestBody::Json(body_map) => json!(body_map).to_string(),  
        RequestBody::Binary(binary_data) => {
            STANDARD.encode(binary_data)
        },
        RequestBody::FormData(form_data) => {
            let params: Vec<String> = form_data
            .iter()
            .flat_map(|(k, v)| {
                match v {
                    FormValue::String(s) => {
                        vec![format!("{}={}", percent_code(k), percent_code(&s))]
                    },
                    FormValue::Vec(vec) => {
                        vec.iter()
                            .map(|s| format!("{}={}", percent_code(k), percent_code(s)))
                            .collect::<Vec<_>>()
                    },
                    FormValue::HashMap(map) => {
                        map.iter()
                            .map(|(sk, sv)| format!("{}={}", percent_code(sk), percent_code(sv)))
                            .collect::<Vec<_>>()
                    },
                }
            })
            .collect();
            params.join("&") 
        },
        RequestBody::None => String::new(),
    };
    
    // リクエストボディの x-acs-content-sha256 を計算し、x-acs-date、x-acs-signature-nonce、および署名するリクエストヘッダーを準備する
    let hashed_request_payload = if body_content.is_empty() {
        sha256_hex("") 
    } else {
        sha256_hex(&body_content) 
    };
    // x-acs-date
    let now_time = current_timestamp().map_err(|e| format!("Get current timestamp failed: {}", e))?;
    let datetime = DateTime::from_timestamp(now_time as i64, 0).ok_or_else(|| format!("Get datetime from timestamp failed: {}", now_time))?;
    let datetime_str = datetime.format("%Y-%m-%dT%H:%M:%SZ").to_string();
    // x-acs-signature-nonce
    let signature_nonce = generate_nonce();
    println!("Signature Nonce: {}", signature_nonce);
    // 署名するリクエストヘッダー
    let sign_header_arr = &[
        "host",
        "x-acs-action",
        "x-acs-content-sha256",
        "x-acs-date",
        "x-acs-signature-nonce",
        "x-acs-version",
    ];
    let sign_headers = sign_header_arr.join(";");
    // 1. 正規リクエストヘッダーを構築する
    let mut headers = HeaderMap::new();
    headers.insert("Host", HeaderValue::from_str(host).unwrap());
    headers.insert("x-acs-action", HeaderValue::from_str(action).unwrap());
    headers.insert("x-acs-version", HeaderValue::from_str(version).unwrap());
    headers.insert("x-acs-date", HeaderValue::from_str(&datetime_str).unwrap());
    headers.insert("x-acs-signature-nonce", HeaderValue::from_str(&signature_nonce).unwrap());
    headers.insert("x-acs-content-sha256", HeaderValue::from_str(&hashed_request_payload).unwrap());
    // 2. 署名するリクエストヘッダーを構築する
    let canonical_query_string = build_sored_encoded_query_string(query_params); // パラメーターをエンコードして連結する
    println!("CanonicalQueryString: {}", canonical_query_string);
    let canonical_request = format!(
        "{}\n{}\n{}\n{}\n\n{}\n{}",
        method.as_str(),
        canonical_uri,
        canonical_query_string,
        sign_header_arr.iter().map(|&header| format!("{}:{}", header, headers[header].to_str().unwrap())).collect::<Vec<_>>().join("\n"),
        sign_headers,
        hashed_request_payload
    );
    println!("Canonical Request: {}", canonical_request);
    // 3. 署名するリクエストヘッダーの SHA-256 ハッシュを計算する
    let result = sha256_hex(&canonical_request);
    // 4. 署名文字列を構築する
    let string_to_sign = format!("ACS3-HMAC-SHA256\n{}", result);
    // 5. 署名を計算する
    let signature = hmac256(access_key_secret.as_bytes(), &string_to_sign)?;
    let data_sign = hex::encode(&signature);
    let auth_data = format!(
        "ACS3-HMAC-SHA256 Credential={},SignedHeaders={},Signature={}",
        access_key_id, sign_headers, data_sign
    );
    // 6. Authorization ヘッダーを構築する
    headers.insert("Authorization", HeaderValue::from_str(&auth_data).unwrap());
    // URL を構築し、リクエストパラメーターを連結する
    let url: String;
    if !query_params.is_empty() {
        url = format!("https://{}{}?{}", host, canonical_uri,canonical_query_string);
    } else {
        url = format!("https://{}{}", host, canonical_uri);
    }        
    // リクエストを送信する呼び出し
    let response = send_request(
        &client,
        method,
        &url,
        headers,
        query_params,                
        &body,                      
        &body_content,                
    ) 
    .await?;
    
    // 応答を読み取る
    let (_, res) = read_response(response).await?;
    Ok(res)
}

/// リクエストを送信する
async fn send_request(
    client: &Client,
    method: Method,
    url: &str,
    headers: HeaderMap,
    query_params: &[(&str, &str)],     // クエリパラメーターを受け取る
    body: &RequestBody,                // ボディのデータ型を決定するために使用
    body_content: &str,                // ボディが空でない場合にボディリクエストパラメーター (FormData/Json/Binary) を受け取る
) -> Result<Response, String> {
    let mut request_builder = client.request(method.clone(), url);
    // リクエストヘッダーを追加する
    for (k, v) in headers.iter() {
        request_builder = request_builder.header(k, v.clone());
    }
     // リクエストボディを追加する
     match body {
        RequestBody::Binary(_) => {
            request_builder = request_builder.header("Content-Type", "application/octet-stream");
            request_builder = request_builder.body(body_content.to_string()); // ここに値を移動
        }
        RequestBody::Json(_) => {
            // ボディがマップで空でない場合、JSON に変換し、body_content 変数に保存し、application/json; charset=utf-8 を設定する
            if !body_content.is_empty() { 
                request_builder = request_builder.body(body_content.to_string());
                request_builder = request_builder.header("Content-Type", "application/json; charset=utf-8");
            }
        }
        RequestBody::FormData(_) => {
            // form-data 型を処理し、content-type を設定する
            if !body_content.is_empty() { 
            request_builder = request_builder.header("Content-Type", "application/x-www-form-urlencoded");
            request_builder = request_builder.body(body_content.to_string());
            }
        }
        RequestBody::None => {
            request_builder = request_builder.body(String::new());
        }
    }
    // リクエストを構築する
    let request = request_builder
        .build()
        .map_err(|e| format!("build request fail: {}", e))?;
    // リクエストを送信する
    let response = client
        .execute(request)
        .await
        .map_err(|e| format!("execute request fail: {}", e))?;
    // 結果を返す
    Ok(response)
}


 /**
     * 
     * 署名の例。必要に応じて main メソッドの例のパラメーターを置き換えてください。
     * <p>
     * API メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得します。
     * 1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、query_params を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
     * 2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/octet-stream または application/json です。RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
     * 2. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/x-www-form-urlencoded です。
*/
#[tokio::main]
async fn main() {
    // HTTP クライアントを作成する
    let client = Client::new();
    // env::var() は環境変数から Access Key ID と Access Key Secret を取得します
    let access_key_id = env::var("ALIBABA_CLOUD_ACCESS_KEY_ID").expect("Cannot get access key id.");
    let access_key_secret = env::var("ALIBABA_CLOUD_ACCESS_KEY_SECRET").expect("Cannot get access key id.");
    let access_key_id: &str = &access_key_id;
    let access_key_secret: &str = &access_key_secret;
    
    // RPC API リクエストの例 1: リクエストパラメーターは "query" にあります   POST
    let method = Method::POST; // リクエストメソッド
    let host = "ecs.cn-hangzhou.aliyuncs.com"; // エンドポイント
    let canonical_uri = "/"; // RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
    let action = "DescribeInstanceStatus"; // API 名
    let version = "2014-05-26"; // API バージョン番号
    let region_id = "cn-hangzhou";
    let instance_ids = vec![
        "i-bp11ht4XXXXXXXX",
        "i-bp16mazXXXXXXXX",
    ];
    let mut query: Vec<(&str, &str)> = Vec::new();
    query.push(("RegionId", region_id));
    for (index, instance_id) in instance_ids.iter().enumerate() {
        let key = format!("InstanceId.{}", index + 1); 
        query.push((Box::leak(key.into_boxed_str()), instance_id)); 
    }
    // クエリパラメーター
    let query_params: &[(&str, &str)] = &query;
    // リクエストボディが空の場合
    let body = RequestBody:: None;
    
    // 複雑なクエリパラメーターを持つ RPC API "in":"query"  POST
    // let method = Method::POST; // リクエストメソッド
    // let host = "tds.cn-shanghai.aliyuncs.com"; // エンドポイント
    // let canonical_uri = "/"; // RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
    // let action = "AddAssetSelectionCriteria"; // API 名
    // let version = "2018-12-03"; // API バージョン番号
    // パラメーターを定義
    // let mut target_op = HashMap::new();
    // target_op.insert("Operation", "add");
    // target_op.insert("Target", "i-2ze1j7ocdXXXXXXXX");
    // パラメーター TargetOperationList を定義し、コレクションにマップ型を渡す
    // let target_operation_list = vec![target_op];
    // パラメーターをフラット化
    // let mut query = flatten_target_ops(target_operation_list, "TargetOperationList");
    // 通常のパラメーター
    // query.push(("SelectionKey", "85a561b7-27d5-47ad-a0ec-XXXXXXXX"));
    // let query_params: &[(&str, &str)] = &query;
    // let body = RequestBody:: None;
    
    // RPC API リクエストの例 2: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ)  POST
    // let method = Method::POST; // リクエストメソッド
    // let host = "ocr-api.cn-hangzhou.aliyuncs.com";
    // let canonical_uri = "/";
    // let action = "RecognizeGeneral";
    // let version = "2021-07-07";
    // リクエストパラメーター "in":"body" バイナリファイル型 
    // let binary_data = std::fs::read("<FILE_PATH>").expect("Failed to read file"); // <FILE_PATH> を実際のファイルパスに置き換える
    // ボディがバイナリ型の場合
    // let body = RequestBody::Binary(binary_data);
    // クエリパラメーターは空
    // let query_params = &[];
 
    // RPC API リクエストの例 3: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ)  POST
    // let method = Method::POST; // リクエストメソッド
    // let host = "mt.aliyuncs.com";
    // let canonical_uri = "/";
    // let action = "TranslateGeneral";
    // let version = "2018-10-12";
    // // FormatType、SourceLanguage、TargetLanguage などのパラメーターはメタデータで "in":"formData" と表示されます
    // let mut form_data = HashMap::new();  // body 型は FormData(HashMap<String, FormValue>)。FormValue は Vec<String>、HashSet<String>、または HashMap<String, String> などをサポートします。FormValue enum にさらに型を追加できます。
    // form_data.insert(String::from("FormatType"),FormValue::String(String::from("text")));
    // form_data.insert(String::from("SourceLanguage"),FormValue::String(String::from("zh")));
    // form_data.insert(String::from("TargetLanguage"),FormValue::String(String::from("en")));
    // form_data.insert(String::from("SourceText"),FormValue::String(String::from("Hello")));
    // form_data.insert(String::from("Scene"),FormValue::String(String::from("general")));
    // // クエリパラメーター
    // let query_params = &[("Context", "Morning")];
    // // ボディが FormData 型の場合、"in":"formdata"
    // let body = RequestBody::FormData(form_data);

    // ROA API POST リクエスト  API: CreateCluster  
    // API リクエスト定数を定義
    // let method = Method::POST; // リクエストメソッド
    // let host = "cs.cn-hangzhou.aliyuncs.com";
    // let canonical_uri = "/clusters";
    // let action = "CreateCluster";
    // let version = "2015-12-15";
    // // リクエストボディパラメーターを設定
    // let mut body_json = HashMap::new();  //  body 型は Json(HashMap<String, Value>)。Value は次の型をサポートします: Value::String("test".to_string()) // 文字列, Value::Number(serde_json::Number::from(42)) // 数値, Value::Bool(true) // ブール値, Value::Null // Null, Value::Array(vec![Value::from(1), Value::from(2), Value::from(3)]) //配列, json!({"nested_key": "nested_value"})
    // body_json.insert(String::from("name"),json!("Test Cluster"));
    // body_json.insert(String::from("region_id"),json!("cn-hangzhou"));
    // body_json.insert(String::from("cluster_type"),json!("ExternalKubernetes"));
    // body_json.insert(String::from("vpcid"),json!("vpc-2zeou1uodXXXXXXXX"));
    // body_json.insert(String::from("container_cidr"),json!("10.X.X.X/X"));
    // body_json.insert(String::from("service_cidr"),json!("10.X.X.X/X"));
    // body_json.insert(String::from("security_group_id"),json!("sg-2ze1a0rlgXXXXXXXX"));
    // body_json.insert(
    //     String::from("vswitch_ids"),
    //     Value::Array(vec![
    //         Value::from("vsw-2zei30dhflXXXXXXXX"),
    //         Value::from("vsw-2zei30dhflXXXXXXXX"),
    //         Value::from("vsw-2zei30dhflXXXXXXXX"),
    //     ]),
    // );
    // // クエリパラメーターは空
    // let query_params = &[];
    // // ボディが Json 型の場合 
    // let body = RequestBody::Json(body_json);

    // ROA API GET リクエスト   API: DeleteCluster  指定されたクラスターのリンクされたリソースをクエリ
    // let method = Method::GET; // リクエストメソッド
    // let host = "cs.cn-hangzhou.aliyuncs.com"; // エンドポイント
    // // リソースパスを連結
    // let uri = format!("/clusters/{}/resources", percent_code("ce196d21571a64be9XXXXXXXX").as_ref()); 
    // let canonical_uri = uri.as_str(); // リソースパス、&str 型に変換
    // let action = "DescribeClusterResources";   // API 名
    // let version = "2015-12-15"; // API バージョン番号
    // // クエリパラメーターを設定
    // let query_params = &[("with_addon_resources", if true { "true" } else { "false" })];  // "true" または "false"
    // // ボディパラメーターを空に設定
    // let body = RequestBody:: None;

    // ROA API DELETE リクエスト   API: DeleteCluster  従量課金クラスターを削除する DELETE リクエスト
    // let method = Method::DELETE;
    // let host = "cs.cn-hangzhou.aliyuncs.com";
    // let uri = format!("/clusters/{}", percent_code("ce0138ff31ad044f8XXXXXXXX").as_ref()); 
    // let canonical_uri = uri.as_str(); // リソースパス、&str 型に変換
    // let action = "DeleteCluster";
    // let version = "2015-12-15";
    // // クエリパラメーター
    // let query_params = &[];
    // // ボディパラメーターが空の場合
    // let body = RequestBody:: None;
    
    // SendSms API
    // let method = Method::POST; // リクエストメソッド
    // let host = "dysmsapi.aliyuncs.com"; // エンドポイント
    // let canonical_uri = "/"; // RPC API にはリソースパスがないため、CanonicalURI としてスラッシュ (/) を使用します
    // let action = "SendSms"; // API 名
    // let version = "2017-05-25"; // API バージョン番号
    // let mut query: Vec<(&str, &str)> = Vec::new();
    // query.push(("PhoneNumbers", "<YOUR_PHONENUMBERS>"));
    // query.push(("TemplateCode", "<YOUR_TEMPLATECODE>"));
    // query.push(("SignName", "<YOUR_SIGNNAME>"));
    // query.push(("TemplateParam", "<YOUR_TEMPLATEPARAM>"));
    // // クエリパラメーター
    // let query_params: &[(&str, &str)] = &query;
    // // リクエストボディが空の場合
    // let body = RequestBody:: None;

    // リクエストを送信する
    match call_api(
        client.clone(),
        method,                                                  // API リクエストメソッド POST/GET/DELETE                                
        host,                                                    // API エンドポイント
        canonical_uri,                                           // API リソースパス
        query_params,                                            // "in":"query" クエリパラメーター
        action,                                                  // API 名
        version,                                                 // API バージョン番号
        body,                                                    // "in":"body" リクエストボディパラメーター、Json/FormData/Binary 型をサポート
        access_key_id,                                           
        access_key_secret,
    )
    .await {
        Ok(response) => println!("Response: {}", response),
        Err(error) => eprintln!("Exception: {}", error),
    }
}

シェルスクリプトの例

#!/bin/bash

accessKey_id="<YOUR-ACCESSKEY-ID>"
accessKey_secret="<YOUR-ACCESSKEY-SECRET>"
algorithm="ACS3-HMAC-SHA256"

# リクエストパラメーター -- 必要に応じてこのセクションを変更してください
httpMethod="POST"
host="dns.aliyuncs.com"
queryParam=("DomainName=example.com" "RRKeyWord=@")
action="DescribeDomainRecords"
version="2015-01-09"
canonicalURI="/"
# body-type または formdata-type のパラメーターをボディ経由で渡す
# body-type パラメーター: body の値は JSON 文字列です: "{'key1':'value1','key2':'value2'}"、署名ヘッダーに content-type:application/json; charset=utf-8 を追加する必要があります。
# body-type パラメーターがバイナリファイルの場合: body を変更する必要はなく、署名ヘッダーに content-type:application/octet-stream を追加し、curl_command に --data-binary パラメーターを追加するだけです。
# formdata-type パラメーター: body パラメーターの形式は "key1=value1&key2=value2" で、署名ヘッダーに content-type:application/x-www-form-urlencoded を追加する必要があります。
body=""

# ISO 8601 形式の UTC 時刻
utc_timestamp=$(date +%s)
utc_date=$(date -u -d @${utc_timestamp} +"%Y-%m-%dT%H:%M:%SZ") 
# x-acs-signature-nonce 乱数
random=$(uuidgen | sed 's/-//g') 

# 署名ヘッダー
headers="host:${host}
x-acs-action:${action}
x-acs-version:${version}
x-acs-date:${utc_date}
x-acs-signature-nonce:${random}"

# URL エンコーディング関数
urlencode() {
    local string="${1}"
    local strlen=${#string}
    local encoded=""
    local pos c o

    for (( pos=0 ; pos<strlen ; pos++ )); do
        c=${string:$pos:1}
        case "$c" in
            [-_.~a-zA-Z0-9] ) o="${c}" ;;
            * )               printf -v o '%%%02X' "'$c"
        esac
        encoded+="${o}"
    done
    echo "${encoded}"
}

# ステップ 1: 正規リクエスト文字列を作成する
# queryParam のすべてのパラメーターをフラット化する
newQueryParam=()

# 各元のパラメーターを走査する
for param in "${queryParam[@]}"; do
    # 等号が含まれているかどうかをチェックして、キーと値のペアであるかどうかを判断する
    if [[ "$param" == *"="* ]]; then
        # キーと値を分割する
        IFS='=' read -r key value <<< "$param"

        # 値を URL エンコードする
        value=$(urlencode "$value")

        # 値がリストであるかどうかをチェックする (括弧を探す)
        if [[ "$value" =~ ^\(.+\)$ ]]; then
            # 括弧を削除する
            value="${value:1:-1}"

            # IFS を使用して値リストを分割する
            IFS=' ' read -ra values <<< "$value"

            # 各値にインデックスを追加する
            index=1
            for val in "${values[@]}"; do
                # 二重引用符を削除する
                val="${val%\"}"
                val="${val#\"}"

                # 新しい配列に追加する
                newQueryParam+=("$key.$index=$val")
                ((index++))
            done
        else
            # リストでない場合は、直接追加する
            newQueryParam+=("$key=$value")
        fi
    else
        # 等号がない場合は、そのまま保持する
        newQueryParam+=("$param")
    fi
done

# 新しいクエリパラメーターを処理してソートする
sortedParams=()
declare -A paramsMap
for param in "${newQueryParam[@]}"; do
    IFS='=' read -r key value <<< "$param"
    paramsMap["$key"]="$value"
done
# キーでソートする
for key in $(echo ${!paramsMap[@]} | tr ' ' '\n' | LC_ALL=C sort); do
    sortedParams+=("$key=${paramsMap[$key]}")
done

# 1.1 正規クエリ文字列を構築する
canonicalQueryString=""
first=true
for item in "${sortedParams[@]}"; do
    [ "$first" = true ] && first=false || canonicalQueryString+="&"
    # 等号が存在するかどうかをチェックする
    if [[ "$item" == *=* ]]; then
        canonicalQueryString+="$item"
    else
        canonicalQueryString+="$item="
    fi
done

# 1.2 リクエストボディを処理する
hashedRequestPayload=$(echo -n "$body" | openssl dgst -sha256 | awk '{print $2}')
headers="${headers}
x-acs-content-sha256:$hashedRequestPayload"

# 1.3 正規リクエストヘッダーを構築する
canonicalHeaders=$(echo "$headers" | grep -E '^(host|content-type|x-acs-)' | while read line; do
    key=$(echo "$line" | cut -d':' -f1 | tr '[:upper:]' '[:lower:]')
    value=$(echo "$line" | cut -d':' -f2-)
    echo "${key}:${value}"
done | sort | tr '\n' '\n')

signedHeaders=$(echo "$headers" | grep -E '^(host|content-type|x-acs-)' | while read line; do
    key=$(echo "$line" | cut -d':' -f1 | tr '[:upper:]' '[:lower:]')
    echo "$key"
done | sort | tr '\n' ';' | sed 's/;$//')

# 1.4 正規リクエストを構築する
canonicalRequest="${httpMethod}\n${canonicalURI}\n${canonicalQueryString}\n${canonicalHeaders}\n\n${signedHeaders}\n${hashedRequestPayload}"
echo -e "canonicalRequest=${canonicalRequest}"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++++"

str=$(echo "$canonicalRequest" | sed 's/%/%%/g')
hashedCanonicalRequest=$(printf "${str}" | openssl sha256 -hex | awk '{print $2}')
# ステップ 2: 署名文字列を作成する
stringToSign="${algorithm}\n${hashedCanonicalRequest}"
echo -e "stringToSign=$stringToSign"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++++"

# ステップ 3: 署名を計算する
signature=$(printf "${stringToSign}" | openssl dgst -sha256 -hmac "${accessKey_secret}" | sed 's/^.* //')
echo -e "signature=${signature}"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++++"

# ステップ 4: Authorization ヘッダーを構築する
authorization="${algorithm} Credential=${accessKey_id},SignedHeaders=${signedHeaders},Signature=${signature}"
echo -e "authorization=${authorization}"

# curl コマンドを構築する
url="https://$host$canonicalURI"
curl_command="curl -X $httpMethod '$url?$canonicalQueryString'"

# リクエストヘッダーを追加する
IFS=$'\n'  # 改行を新しい IFS として設定
for header in $headers; do
    curl_command="$curl_command -H '$header'"
done
curl_command+=" -H 'Authorization:$authorization'"
# body-type パラメーターがバイナリファイルの場合、次の行をコメントアウトする
curl_command+=" -d '$body'"
# body-type パラメーターがバイナリファイルの場合、次の行のコメントを解除する
#curl_command+=" --data-binary @"/root/001.png" "

echo "$curl_command"
# curl コマンドを実行する
eval "$curl_command"

C の例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
#include <stdint.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <openssl/rand.h>
#include <curl/curl.h>

// getenv() は環境変数から Access Key ID と Access Key Secret を取得します
#define ACCESS_KEY_ID getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
#define ACCESS_KEY_SECRET getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
#define ALGORITHM "ACS3-HMAC-SHA256"
#define BUFFER_SIZE 4096

// ソート用の構造体
typedef struct {
    char key[256];
    char value[256];
} KeyValuePair;

// 比較関数: キーを辞書順でソート
int compare_pairs(const void *a, const void *b) {
    return strcmp(((const KeyValuePair *)a)->key, ((const KeyValuePair *)b)->key);
}

// URL エンコーディング
char* percentEncode(const char* str) {
    if (str == NULL) {
        fprintf(stderr, "Input string cannot be null\n");
        return NULL;
    }
    size_t len = strlen(str);
    char* encoded = (char*)malloc(len * 3 + 1);
    if (encoded == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        free(encoded); 
        return NULL;
    }
    char* ptr = encoded;
    for (size_t i = 0; i < len; i++) {
        unsigned char c = (unsigned char)str[i];
        if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
            *ptr++ = c;
        } else {
            ptr += sprintf(ptr, "%%%02X", c);
        }
    }
    *ptr = '\0'; 
    char* finalEncoded = malloc(strlen(encoded) + 1);
    if (finalEncoded) {
        char* fptr = finalEncoded;
        for (size_t j = 0; j < strlen(encoded); j++) {
            if (encoded[j] == '+') {
                strcpy(fptr, "%20");
                fptr += 3; 
            } else if (encoded[j] == '*') {
                strcpy(fptr, "%2A");
                fptr += 3;
            } else if (encoded[j] == '~') {
                *fptr++ = '~';
            } else {
                *fptr++ = encoded[j];
            }
        }
        *fptr = '\0'; 
    }

    free(encoded); 
    return finalEncoded;
}

/**
 * @brief クエリパラメーターを URL エンコードし、辞書順にソートして、正規クエリ文字列を生成します。
 * @param query_params 元のクエリパラメーター文字列 (例: "key1=value1&key2=value2")。
 * @return char* ソートおよびエンコードされた正規クエリ文字列 (呼び出し元はメモリを解放する必要があります)。
 */
char* generate_sorted_encoded_query(const char* query_params) {
    if (query_params == NULL || strlen(query_params) == 0) {
        return strdup(""); // 空のパラメーターには空の文字列を返す
    }

    KeyValuePair pairs[100]; // 最大 100 個のキーと値のペアをサポート
    int pair_count = 0;

    char* copy = strdup(query_params);
    if (!copy) {
        fprintf(stderr, "Memory allocation failed\n");
        return NULL;
    }

    char* token = NULL;
    char* saveptr = NULL;
    token = strtok_r(copy, "&", &saveptr);

    while (token != NULL && pair_count < 100) {
        char* eq = strchr(token, '=');
        if (eq) {
            size_t key_len = eq - token;
            char key[256], value[256];

            strncpy(key, token, key_len);
            key[key_len] = '\0';

            const char* val = eq + 1;
            strncpy(value, val, sizeof(value) - 1);
            value[sizeof(value) - 1] = '\0';

            char* encoded_key = percentEncode(key);
            char* encoded_value = percentEncode(value);

            strncpy(pairs[pair_count].key, encoded_key, sizeof(pairs[pair_count].key));
            strncpy(pairs[pair_count].value, encoded_value, sizeof(pairs[pair_count].value));
            pair_count++;

            free(encoded_key);
            free(encoded_value);
        }
        token = strtok_r(NULL, "&", &saveptr);
    }

    free(copy);

    // キーでソート
    qsort(pairs, pair_count, sizeof(KeyValuePair), compare_pairs);

    // ソートされたクエリ文字列を連結
    char* query_sorted = malloc(BUFFER_SIZE);
    if (!query_sorted) {
        fprintf(stderr, "Memory allocation failed\n");
        return NULL;
    }
    query_sorted[0] = '\0';

    for (int i = 0; i < pair_count; ++i) {
        if (i == 0) {
            snprintf(query_sorted, BUFFER_SIZE, "%s=%s", pairs[i].key, pairs[i].value);
        } else {
            char temp[512];
            snprintf(temp, sizeof(temp), "&%s=%s", pairs[i].key, pairs[i].value);
            strncat(query_sorted, temp, BUFFER_SIZE - strlen(query_sorted) - 1);
        }
    }

    return query_sorted;
}

// HMAC-SHA256 計算
void hmac256(const char *key, const char *message, char *output) {
    unsigned char hmac[SHA256_DIGEST_LENGTH];
    unsigned int result_len;
    HMAC(EVP_sha256(), key, strlen(key), (unsigned char *)message, strlen(message), hmac, &result_len);
    for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
        sprintf(output + (i * 2), "%02x", hmac[i]);
    }
    output[SHA256_DIGEST_LENGTH * 2] = '\0';
}
// SHA-256 ハッシュを計算
void sha256_hex(const char *input, char *output) {
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256((unsigned char *)input, strlen(input), hash);
    for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
        sprintf(output + (i * 2), "%02x", hash[i]);
    }
    output[SHA256_DIGEST_LENGTH * 2] = '\0';
}
// x-acs-signature-nonce を生成するために使用
void generate_uuid(char *uuid, size_t size) {
    if (size < 37) { 
        fprintf(stderr, "Buffer size too small for UUID\n");
        return;
    }
    unsigned char random_bytes[16];
    RAND_bytes(random_bytes, sizeof(random_bytes));
    random_bytes[6] &= 0x0f; // 上位 4 ビットを保持
    random_bytes[6] |= 0x40; // バージョンを 4 に設定
    random_bytes[8] &= 0x3f; // 上位 2 ビットを保持
    random_bytes[8] |= 0x80; // バリアントを 10xx に設定
    snprintf(uuid, size,
             "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
             random_bytes[0], random_bytes[1], random_bytes[2], random_bytes[3],
             random_bytes[4], random_bytes[5], random_bytes[6], random_bytes[7],
             random_bytes[8], random_bytes[9], random_bytes[10], random_bytes[11],
             random_bytes[12], random_bytes[13], random_bytes[14], random_bytes[15]);
}
// ファイルをアップロード
size_t read_file(const char *file_path, char **buffer) {
    FILE *file = fopen(file_path, "rb");
    if (!file) {
        fprintf(stderr, "Cannot open file %s\n", file_path);
        return 0; // 読み取り失敗
    }
    fseek(file, 0, SEEK_END);
    size_t file_size = ftell(file);
    fseek(file, 0, SEEK_SET);

    *buffer = (char *)malloc(file_size);
    if (!*buffer) {
        fprintf(stderr, "Failed to allocate memory for file buffer\n");
        fclose(file);
        return 0; // 読み取り失敗
    }
    fread(*buffer, 1, file_size, file);
    fclose(file);
    return file_size; // 読み取ったバイト数を返す
}
// Authorization を計算
char* get_authorization(const char *http_method, const char *canonical_uri, const char *host,
                       const char *x_acs_action, const char *x_acs_version, const char *query_params,
                       const char *body, char *authorization_header,
                        char *hashed_payload, char *x_acs_date, char *uuid) {
    // x-acs-signature-nonce、x-acs-date、x-acs-content-sha256、および署名する文字列を準備
    generate_uuid(uuid, 37);
    // x-acs-date の形式は yyyy-MM-ddTHH:mm:ssZ、例: 2025-04-17T07:19:10Z
    time_t now = time(NULL);
    struct tm *utc_time = gmtime(&now);
    strftime(x_acs_date, 64, "%Y-%m-%dT%H:%M:%SZ", utc_time);
    // 署名する文字列
    char signed_headers[] = "host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version";
    // x-acs-content-sha256 
    sha256_hex(body ? body : "", hashed_payload);
    printf("Generated x-acs-content-sha256: %s\n", hashed_payload);
    // 1. 正規リクエストヘッダーを構築
    char canonical_headers[BUFFER_SIZE];
    snprintf(canonical_headers, sizeof(canonical_headers),
             "host:%s\nx-acs-action:%s\nx-acs-content-sha256:%s\nx-acs-date:%s\nx-acs-signature-nonce:%s\nx-acs-version:%s",
              host, x_acs_action, hashed_payload, x_acs_date, uuid, x_acs_version);
    printf("Canonical Headers:\n%s\n", canonical_headers);

    // 2. 署名するリクエストヘッダーを構築
    // クエリパラメーターをソートしてエンコード
    char* sorted_query_params = generate_sorted_encoded_query(query_params);
    if (!sorted_query_params) {
      fprintf(stderr, "Failed to generate sorted query string\n");
      return NULL;
    }
    char canonical_request[BUFFER_SIZE];
    snprintf(canonical_request, sizeof(canonical_request),
         "%s\n%s\n%s\n%s\n\n%s\n%s",
         http_method,
         canonical_uri,
         sorted_query_params ? sorted_query_params : "",
         canonical_headers,
         signed_headers,
         hashed_payload);
    printf("Canonical Request:\n%s\n", canonical_request);

    // 3. 正規リクエストの SHA-256 ハッシュを計算
    char hashed_canonical_request[SHA256_DIGEST_LENGTH * 2 + 1];
    sha256_hex(canonical_request, hashed_canonical_request);
    printf("hashedCanonicalRequest: %s\n", hashed_canonical_request);
    // 4. 署名文字列を構築 
    char string_to_sign[BUFFER_SIZE];
    snprintf(string_to_sign, sizeof(string_to_sign), "%s\n%s", ALGORITHM, hashed_canonical_request);
    printf("stringToSign:\n%s\n", string_to_sign);
    // 5. 署名を計算 
    char signature[SHA256_DIGEST_LENGTH * 2 + 1];
    hmac256(ACCESS_KEY_SECRET, string_to_sign, signature);
    printf("Signature: %s\n", signature);
    // 6. Authorization ヘッダーを構築
    snprintf(authorization_header, BUFFER_SIZE,
             "%s Credential=%s,SignedHeaders=%s,Signature=%s",
             ALGORITHM, ACCESS_KEY_ID, signed_headers, signature);
    printf("Authorization: %s\n", authorization_header);

    return sorted_query_params;
}
// リクエストを送信
void call_api(const char *http_method, const char *canonical_uri, const char *host,
              const char *x_acs_action, const char *x_acs_version, const char *query_params,
              const char *body,const char *content_type, size_t body_length) {
    // 署名計算に必要なパラメーター値を取得
    char authorization_header[BUFFER_SIZE];
    char hashed_payload[SHA256_DIGEST_LENGTH * 2 + 1];
    char x_acs_date[64];
    char uuid[37];
    // 1. curl を初期化
    CURL *curl = curl_easy_init();
    if (!curl) {
        fprintf(stderr, "curl_easy_init() failed\n");
        goto cleanup;
    }
    // 2. 署名を計算 (ソートおよびエンコードされたクエリパラメーターを返す)
    char *signed_query_params = get_authorization(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, authorization_header, hashed_payload, x_acs_date, uuid);
    // 3. リクエストパラメーターを追加 
    char url[BUFFER_SIZE];
    if (signed_query_params && strlen(signed_query_params) > 0) {
        snprintf(url, sizeof(url), "https://%s%s?%s", host, canonical_uri, signed_query_params);
    } else {
        snprintf(url, sizeof(url), "https://%s%s", host, canonical_uri);
    }
    printf("Request URL: %s\n", url);
    // メモリを解放
    if (signed_query_params) {
        free(signed_query_params); // メモリを解放
    }

    // 4. ヘッダーを追加
    struct curl_slist *headers = NULL;
    char header_value[BUFFER_SIZE];
    snprintf(header_value, sizeof(header_value), "Content-Type: %s", content_type);
    headers = curl_slist_append(headers, header_value);
    snprintf(header_value, sizeof(header_value), "Authorization: %s", authorization_header);
    headers = curl_slist_append(headers, header_value);
    snprintf(header_value, sizeof(header_value), "host: %s", host);
    headers = curl_slist_append(headers, header_value);
    snprintf(header_value, sizeof(header_value), "x-acs-action: %s", x_acs_action);
    headers = curl_slist_append(headers, header_value);
    snprintf(header_value, sizeof(header_value), "x-acs-content-sha256: %s", hashed_payload);
    headers = curl_slist_append(headers, header_value);
    snprintf(header_value, sizeof(header_value), "x-acs-date: %s", x_acs_date);
    headers = curl_slist_append(headers, header_value);
    snprintf(header_value, sizeof(header_value), "x-acs-signature-nonce: %s", uuid);
    headers = curl_slist_append(headers, header_value);
    snprintf(header_value, sizeof(header_value), "x-acs-version: %s", x_acs_version);
    headers = curl_slist_append(headers, header_value);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, http_method);
    curl_easy_setopt(curl, CURLOPT_URL, url);
    // その他の CURL 設定: SSL 検証を無効化、デバッグ情報を追加
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
    // 5. ボディを追加
    if (body) {
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body_length); 
        if (strcmp(content_type, "application/octet-stream") == 0) {
            curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
        } else if (strcmp(content_type, "application/x-www-form-urlencoded") == 0) {
            curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
        } else if (strcmp(content_type, "application/json; charset=utf-8") == 0) {
            curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
        }
    }
    printf("RequestBody:%s\n",body);
    // 6. リクエストを送信
    CURLcode res = curl_easy_perform(curl);
    if (res != CURLE_OK) {
        fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        goto cleanup;
    }
cleanup:
    if (headers) curl_slist_free_all(headers);
    if (curl) curl_easy_cleanup(curl);
}
/**
*
     * 署名の例。必要に応じて main メソッドの例のパラメーターを置き換えてください。
     * <p>
     * API メタデータからリクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーターの型 (type)、リクエストパラメーターの場所 (in) を取得します。
     * 1. リクエストパラメーターがメタデータで "in":"query" と表示されている場合、query_params を使用してパラメーターを渡します。注意: RPC API の場合、このタイプのパラメーターは content-type を application/x-www-form-urlencoded としてボディ経由で渡すこともできます。例 3 を参照してください。
     * 2. リクエストパラメーターがメタデータで "in": "body" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/octet-stream または application/json です。RPC API の場合、application/json の使用は推奨されません。代わりに例 3 を使用できます。
     * 2. リクエストパラメーターがメタデータで "in": "formData" と表示されている場合、ボディ経由でパラメーターを渡します。MIME タイプは application/x-www-form-urlencoded です。
*/
int main() {
    // 応答形式を UTF-8 に設定
    SetConsoleOutputCP(CP_UTF8);
    srand((unsigned int)time(NULL));

    /**
      * RPC API リクエストの例: リクエストパラメーターは "query" にあり、クエリタイプは複雑です。
    */   
    const char *http_method = "POST";
    const char *canonical_uri = "/";
    const char *host = "tds.cn-shanghai.aliyuncs.com";
    const char *x_acs_action = "AddAssetSelectionCriteria";
    const char *x_acs_version = "2018-12-03";

    // パラメーター SelectionKey を定義、文字列型
    const char *selection_key = "85a561b7-27d5-47ad-a0ec-XXXXXXXX";
    // パラメーター TargetOperationList を定義、ターゲットオブジェクトのコレクション (拡張可能)
    struct {
        const char *operation;
        const char *target;
    } targetOperation_list[] = {
        {"add", "i-2ze1j7ocdg9XXXXXXXX"},
        // さらにエントリを追加可能
        // {"add", "i-abc123xyzXXXXX"},
    };

    int count = sizeof(targetOperation_list) / sizeof(targetOperation_list[0]);
    KeyValuePair pairs[100]; // 元のキーと値を保存
    int pair_count = 0;

    for (int i = 0; i < count; ++i) {
      char op_key[128], target_key[128];
      snprintf(op_key, sizeof(op_key), "TargetOperationList.%d.Operation", i + 1);
      snprintf(target_key, sizeof(target_key), "TargetOperationList.%d.Target", i + 1);

      strncpy(pairs[pair_count].key, op_key, sizeof(pairs[pair_count].key));
      strncpy(pairs[pair_count].value, targetOperation_list[i].operation, sizeof(pairs[pair_count].value));
      pair_count++;

      strncpy(pairs[pair_count].key, target_key, sizeof(pairs[pair_count].key));
      strncpy(pairs[pair_count].value, targetOperation_list[i].target, sizeof(pairs[pair_count].value));
      pair_count++;
}
    // SelectionKey パラメーターを追加
    snprintf(pairs[pair_count].key, sizeof(pairs[pair_count].key), "SelectionKey");
    snprintf(pairs[pair_count].value, sizeof(pairs[pair_count].value), "%s", selection_key);
    pair_count++;

    // ソートとエンコーディングは get_authorization() で行われます
    qsort(pairs, pair_count, sizeof(KeyValuePair), compare_pairs);

    // 元のクエリ文字列を構築 (エンコードなし)
    char query_params[BUFFER_SIZE] = {0};
    for (int i = 0; i < pair_count; ++i) {
      if (i == 0) {
        snprintf(query_params, sizeof(query_params), "%s=%s", pairs[i].key, pairs[i].value);
     } else {
        char temp[512];
        snprintf(temp, sizeof(temp), "&%s=%s", pairs[i].key, pairs[i].value);
        strncat(query_params, temp, sizeof(query_params) - strlen(query_params) - 1);
    }
}
    const char *body = ""; // リクエストボディは空
    const char *content_type = "application/json; charset=utf-8";
    call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));

    /**
      * RPC API リクエストの例: リクエストパラメーターは "query" にあります
    */
    // API リクエストパラメーターを定義
    // const char *http_method = "POST";
    // const char *canonical_uri = "/";
    // const char *host = "ecs.cn-hangzhou.aliyuncs.com";
    // const char *x_acs_action = "DescribeInstanceStatus";
    // const char *x_acs_version = "2014-05-26";
    // // パラメーター InstanceId を定義、配列。InstanceId はオプションのパラメーターです。
    // const char *instance_ids[] = {
    //     "i-bp11ht4hXXXXXXXX",
    //     "i-bp16maz3XXXXXXXX"
    // };
    // // InstanceId 配列を連結
    // char InstanceId[BUFFER_SIZE];
    // snprintf(InstanceId, sizeof(InstanceId),
    //          "InstanceId.1=%s&InstanceId.2=%s",
    //         instance_ids[0],
    //         instance_ids[1]);
    // // クエリパラメーターを定義。必須パラメーター: RegionId=cn-hangzhou。const char *query_params = "RegionId=cn-hangzhou";
    // char query_params[BUFFER_SIZE];
    // snprintf(query_params, sizeof(query_params),
    //          "%s&RegionId=cn-hangzhou", InstanceId);
    // const char *body = "";
    // const char *content_type = "application/json; charset=utf-8";
    // call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));

      /**
        * RPC API リクエストの例: リクエストパラメーターは "body" にあります (ファイルアップロードシナリオ)
      */
    // 読み取ったファイルコンテンツを保存するためのポインターを宣言
    // char *body = NULL;
    // size_t body_length = read_file("<YOUR_FILE_PATH>", &body);
    // if (body_length > 0) {
    //   const char *http_method = "POST";
    //   const char *canonical_uri = "/";
    //   const char *host = "ocr-api.cn-hangzhou.aliyuncs.com";
    //   const char *x_acs_action = "RecognizeGeneral";
    //   const char *x_acs_version = "2021-07-07";
    //   const char *query_params = "";
    //   const char *content_type = "application/octet-stream";
    //   call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, body_length);
    //   free(body);
    // } else {
    //   fprintf(stderr, "File read error\n");
    // }

      /**
       * RPC API リクエストの例: リクエストパラメーターは "formData" または "in":"body" にあります (非ファイルアップロードシナリオ)
       */
    // const char *http_method = "POST";
    // const char *canonical_uri = "/";
    // const char *host = "mt.aliyuncs.com";
    // const char *x_acs_action = "TranslateGeneral";
    // const char *x_acs_version = "2018-10-12";
    // char query_params[BUFFER_SIZE];
    // snprintf(query_params, sizeof(query_params), "Context=%s", "Morning");
    // const char *format_type = "text";
    // const char *source_language = "zh";
    // const char *target_language = "en";
    // const char *source_text = "Hello";
    // const char *scene = "general";
    // char body[BUFFER_SIZE];
    // snprintf(body, sizeof(body),
    // "FormatType=%s&SourceLanguage=%s&TargetLanguage=%s&SourceText=%s&Scene=%s",
    // percentEncode(format_type), percentEncode(source_language), percentEncode(target_language),
    // percentEncode(source_text), percentEncode(scene));
    // const char *content_type = "application/x-www-form-urlencoded";
    // printf("formdate_body: %s\n", body);
    // call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));

   // RPC API リクエストの例 3: リクエストパラメーターは "formData" にあります
//    const char *http_method = "POST";
//    const char *canonical_uri = "/";
//    const char *host = "sasti.aliyuncs.com";
//    const char *x_acs_action = "AskTextToTextMsg";
//    const char *x_acs_version = "2020-05-12";
//    // クエリ
//    const char *query_params = "";
//    // ボディ
//    const char *Memory = "false";
//    const char *Stream = "true";
//    const char *ProductCode = "sddp_pre";
//    const char *Feature = "{}";
//    const char *Model = "yunsec-llm-latest";
//    const char *Type = "Chat";
//    const char *TopP = "0.9";
//    const char *Temperature = "0.01";
//    const char *Prompt = "Who are you";
//    const char *Application = "sddp_pre";
//    char body[BUFFER_SIZE];
//    snprintf(body, sizeof(body),
//            "Memory=%s&Stream=%s&ProductCode=%s&Feature=%s&Model=%s&Type=%s&TopP=%s&Temperature=%s&Prompt=%s&Application=%s",
//            Memory, Stream, ProductCode, Feature, Model, Type, TopP, Temperature, Prompt, Application);
//    const char *content_type = "application/x-www-form-urlencoded";
//    printf("formdate_body: %s\n", body);
//    call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));

      /**
        * ROA API POST リクエスト "in" "body"
      */
//    const char *http_method = "POST";
//    const char *canonical_uri = "/clusters";
//    const char *host = "cs.cn-beijing.aliyuncs.com";
//    const char *x_acs_action = "CreateCluster";
//    const char *x_acs_version = "2015-12-15";
//    const char *query_params = "";
//    char body[BUFFER_SIZE];
//    snprintf(body, sizeof(body),
//             "{\"name\":\"%s\",\"region_id\":\"%s\",\"cluster_type\":\"%s\","
//             "\"vpcid\":\"%s\",\"container_cidr\":\"%s\","
//             "\"service_cidr\":\"%s\",\"security_group_id\":\"%s\","
//             "\"vswitch_ids\":[\"%s\"]}",
//             "Test Cluster", "cn-beijing", "ExternalKubernetes",
//             "vpc-2zeou1uod4yXXXXXXXX", "10.X.X.X/XX",
//             "10.X.X.X/XX", "sg-2ze1a0rlgeXXXXXXXX",
//             "vsw-2zei30dhflXXXXXXXX");
//    const char *content_type = "application/json; charset=utf-8";
//    call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));

      /**
        * ROA API GET リクエスト
      */
//    const char *http_method = "GET";
//    char canonical_uri[BUFFER_SIZE];
//    snprintf(canonical_uri, sizeof(canonical_uri), "/clusters/%s/resources", percentEncode("cd1f5ba0dbfa144XXXXXXXX"));
//    const char *host = "cs.cn-beijing.aliyuncs.com";
//    const char *x_acs_action = "DescribeClusterResources";
//    const char *x_acs_version = "2015-12-15";
//    const char *query_params = "with_addon_resources=true";
//    const char *body = "";
//    const char *content_type = "";
//    call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));

      /**
        *  ROA API DELETE リクエスト
      */
//    const char *http_method = "DELETE";
//    char canonical_uri[BUFFER_SIZE];
//    snprintf(canonical_uri, sizeof(canonical_uri), "/clusters/%s", percentEncode("cd1f5ba0dbfa144XXXXXXXX"));
//    const char *host = "cs.cn-beijing.aliyuncs.com";
//    const char *x_acs_action = "DeleteCluster";
//    const char *x_acs_version = "2015-12-15";
//    const char *query_params = "";
//    const char *body = "";
//    const char *content_type = "";
//    call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));



    // 生成された値を保存する変数
    char authorization_header[BUFFER_SIZE];
    char hashed_payload[SHA256_DIGEST_LENGTH * 2 + 1];
    char x_acs_date[64];
    char uuid[37];
    return 0;
}

よくある質問

署名が失敗した場合に「Specified signature does not match our calculation.」または「The request signature does not conform to Aliyun standards.」というエラーが表示されるのはなぜですか?

原因:

私たちの経験上、ほとんどの問題は正規リクエストの作成中に発生します。一般的な原因は次のとおりです:

  • 不正な AccessKey 構成。

  • リクエストボディでクエリパラメーターを渡すなど、不正なパラメーターの渡し場所。

  • 正規クエリ文字列のパラメーターがアルファベット順にソートされていない。

  • 署名付きヘッダーがアルファベット順にソートされていない。

  • スペースが %20 としてエンコードされていない。

  • 余分な URL エンコーディングが実行された。署名計算プロセス中、URL エンコーディングはパスパラメーターと標準クエリ文字列を処理するときに一度だけ実行する必要があります。たとえば、エラーメッセージに複数の %25 文字が含まれている場合、% 文字がエンコードされたことを示します。

解決策:

説明

まず、固定パラメーターの例に基づいて、ローカルコードの計算結果が正しいかどうかを確認します。

  1. エラーメッセージの正規リクエストとローカルで計算された正規リクエストを比較します。それらが異なる場合は、このトピックに記載されている一般的な原因とステップ 1: 正規リクエストを作成するの説明を参照して、コードを注意深く確認してください。

  2. 正規リクエストが一致する場合、エラーメッセージの署名文字列がローカルで計算された署名文字列と異なるかどうかを確認します。異なる場合、ハッシュアルゴリズムが不正である可能性があります。

  3. 署名文字列が一致する場合、原因は不正な AccessKey Secret または不正な暗号化アルゴリズムである可能性があります。

  4. 問題が解決しない場合は、お問い合わせください。

Postman でテストするにはどうすればよいですか?

Postman を使用して OpenAPI を直接呼び出すことはできません。Postman でテストしたい場合は、次の手順を実行します:

  1. 署名メカニズムに基づいて、コードまたはスクリプトを使用してAuthorization 値を計算します。

  2. 正規ヘッダーからリクエストヘッダー情報を Postman の「Headers」セクションにコピーし、「Headers」セクションに Authorization 情報を入力します。次の表に例を示します。

    キー

    値の例

    host

    dysmsapi.aliyuncs.com

    x-acs-action

    SendSms

    x-acs-content-sha256

    e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

    x-acs-date

    2025-04-16T07:45:55Z

    x-acs-signature-nonce

    315484d3-b129-4966-974a-699b7ee56647

    x-acs-version

    2017-05-25

    Authorization

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

  3. パラメーターのタイプに基づいて Postman でパラメーターを設定します:

    説明

    パラメーターの順序は、署名計算に使用される順序と一致している必要があります。

    • クエリパラメーターの場合は、Params セクションに入力します。

    • 本文パラメーターの場合は、Body セクションに入力します。

リクエストパラメーターを渡すにはどうすればよいですか?

API メタデータでは、in フィールドが各パラメーターの場所を定義します。この場所によって、パラメーターの渡し方が決まります。

パラメーターの場所

説明

content-type

"in": "query"

クエリパラメーターは、リクエスト URL の末尾にある疑問符 (?) の後に表示されます。異なる name=value ペアは、アンパサンド (&) で区切られます。

必須ではありません。渡される場合、値は application/json です。

"in": "formData"

フォームパラメーター。パラメーターは key1=value1&key2=value2&key3=value3 のような文字列に連結し、リクエストボディを介して渡す必要があります。さらに、リクエストパラメーターのタイプが array または object の場合、value をインデックス付きのキーと値のペアに変換する必要があります。たとえば、object タイプの値 {"key":["value1","value2"]}{"key.1":"value1","key.2":"value2"} に変換する必要があります。

必須です。値は content-type=application/x-www-form-urlencoded です。

"in": "body"

ボディパラメーター。リクエストボディを介して渡されます。

必須です。content-type の値は、リクエストコンテンツのタイプによって異なります。例:

  • リクエストコンテンツが JSON データの場合、content-type は application/json です。

  • リクエストコンテンツがバイナリファイルストリームの場合、content-type は application/octet-stream です。

API メタデータの style の値が RPC または ROA ではない場合、API スタイルはどのように決定されますか?

RPC または ROA スタイルは CanonicalURI の値に影響します。style が別のタイプの場合、CanonicalURI の値は API メタデータの概要にある path パラメーターによって決定されます。path パラメーターが存在する場合、その値が CanonicalURI として使用されます。それ以外の場合、CanonicalURI はフォワードスラッシュ (/) です。たとえば、ACK クラスターリストをクエリするための API メタデータでは、path の値は /api/v1/clusters であり、そのため CanonicalURI の値は /api/v1/clusters になります。image

配列型またはオブジェクト型のパラメーターを渡すにはどうすればよいですか?

リクエストパラメーターが複雑なデータ構造である場合、パラメーターの値をインデックス付きのキーと値のペアに変換する必要があります。

例 1: 次のパラメーター {"InstanceId":["i-bp10igfmnyttXXXXXXXX","i-bp1incuofvzxXXXXXXXX","i-bp1incuofvzxXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX"]} は、次のように変換する必要があります。

{
    "InstanceId.1": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.10": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.11": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.12": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.2": "i-bp1incuofvzxXXXXXXXX",
    "InstanceId.3": "i-bp1incuofvzxXXXXXXXX",
    "InstanceId.4": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.5": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.6": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.7": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.8": "i-bp10igfmnyttXXXXXXXX",
    "InstanceId.9": "i-bp10igfmnyttXXXXXXXX"
}

例 2: 次のパラメーター {"ImageId":"win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd","RegionId":"cn-shanghai","Tag":[{"tag1":"value1","tag2":"value2"}]} は、次のように変換する必要があります。

{
    "ImageId":"win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd",
    "RegionId":"cn-shanghai",
    "Tag.1.tag1":"value1",
    "Tag.1.tag2":"value2"
}

API バージョン (x-acs-version) を取得する方法

  1. Alibaba Cloud OpenAPI Developer Portal に移動し、呼び出したい API に対応するクラウドプロダクトを選択します。この例では ECS を使用します。image

  2. クラウドプロダクトのホームページで、推奨 API バージョンを表示できます。たとえば、ECS の推奨 API バージョンは 2014-05-26 です。

    image

自己署名時に GET で正常にデバッグできる場合、POST を使用できますか。

  • RPC API の場合、GET リクエストと POST リクエストの両方がサポートされています。

  • ROA API の場合、1 つのリクエストメソッドのみがサポートされています。

API でサポートされているリクエストメソッドを取得する方法の詳細については、「OpenAPI メタデータ」をご参照ください。

API の呼び出し時に「You are not authorized to do this operation.」というエラーが表示されるのはなぜですか?

原因: 使用している AccessKey に対応する RAM ユーザーが、この API を呼び出すために必要な権限を持っていません。

解決策: 詳細については、「コード 403、You are not authorized to do this operation. Action: xxxx。」をご参照ください。

AccessKey ペアを取得するにはどうすればよいですか?

AccessKey ペアは、Alibaba Cloud がユーザーに提供する永続的なアクセス資格情報です。これは AccessKey ID と AccessKey Secret で構成されています。API を呼び出して Alibaba Cloud リソースにアクセスすると、システムは、リクエストに含まれる AccessKey ID と、AccessKey Secret を使用して生成された署名に基づいて、ID を認証し、リクエストの正当性を検証します。詳細については、「RAM ユーザーの AccessKey ペアを作成する」をご参照ください。

お問い合わせ

署名の計算時に解決できない問題が発生した場合は、ID 147535001692 の DingTalk グループに参加して、担当エンジニアに支援を依頼してください。

説明

署名の計算に関係のない問題については、このグループに参加しないでください。参加した場合、効果的な応答が得られない可能性があります。