このトピックでは、署名方法 V3 と HTTP リクエストを使用して Alibaba Cloud API 操作を呼び出す方法について説明します。
署名方法 V3 は、OpenAPI Explorer が提供する SDK を使用する Alibaba Cloud サービスの API 操作を呼び出すために使用できます。署名方法 V2 を使用している場合は、署名方法 V3 に切り替えることをお勧めします。
HTTP リクエスト構文
次の表に、Alibaba Cloud API リクエストのコンポーネントを示します。
コンポーネント | 必須 | 説明 | 例 |
プロトコル | はい | API リクエストを送信するために使用されるプロトコル。使用するプロトコルに関する情報は、各 Alibaba Cloud サービスの API リファレンスを参照してください。 | https:// |
エンドポイント | はい | Alibaba Cloud サービス API のエンドポイント。各 Alibaba Cloud サービスの API リファレンスを参照して、さまざまなリージョンにおけるサービスのエンドポイントを確認できます。 | ecs.cn-shanghai.aliyuncs.com |
resource_URI_parameters | はい | アクセスするリソースの URL。リソースパスとリクエストパラメータが含まれます。 | ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai |
リクエストヘッダー | はい | 共通リクエストヘッダー。ほとんどの場合、API バージョン番号、エンドポイント、認証情報などの情報が含まれます。詳細については、このトピックの「リクエストヘッダー」セクションを参照してください。 | 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 のメタデータで取得できます。詳細については、「API メタデータ」を参照してください。 | |
HTTPMethod | はい | リクエストメソッド。リクエストメソッドは、API のメタデータで取得できます。詳細については、「API メタデータ」を参照してください。 | POST |
リクエストヘッダー
Alibaba Cloud API 操作を呼び出す際に共通リクエストヘッダーに含める必要がある情報について、次の表で説明します。
ヘッダー | タイプ | 必須 | 説明 | 例 |
host | 文字列 | はい | Alibaba Cloud サービス API のエンドポイント。詳細については、このトピックの「HTTP リクエスト構文」セクションを参照してください。 | ecs.cn-shanghai.aliyuncs.com |
x-acs-action | 文字列 | はい | 実行する操作。OpenAPI ポータルで、呼び出す API 操作を検索できます。 | RunInstances |
x-acs-content-sha256 | 文字列 | はい | リクエスト本文のハッシュ値。ハッシュ値は Base16 でエンコードされます。このパラメーターの値は、HashedRequestPayload パラメーターの値と同じです。 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 |
x-acs-date | String | はい | リクエストのタイムスタンプ。ISO 8601 標準の yyyy-MM-ddTHH:mm:ssZ フォーマットで時間を指定します。時間は UTC である必要があります。例: 2018-01-01T12:00:00Z。タイムスタンプは、リクエストが送信される 15 分前以内である必要があります。 | 2023-10-26T10:22:32Z |
x-acs-signature-nonce | 文字列 | はい | ネットワークのリプレイ攻撃を防ぐために使用される一意の乱数。リクエストごとに異なる数値を使用する必要があります。 | 3156853299f313e23d1673dc12e1703d |
x-acs-content-sha256 | 文字列 | はい | API のバージョン番号。API バージョン番号の詳細については、この Topic のx-acs-version の API バージョンを取得する方法セクションをご参照ください。 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 |
x-acs-date | 文字列 | はい | リクエストを検証するために使用される認証情報。フォーマット: Authorization: SignatureAlgorithm Credential=AccessKeyId,SignedHeaders=SignedHeaders,Signature=Signature。 SignatureAlgorithm: 署名文字列の暗号化メソッド。 値を ACS3-HMAC-SHA256 に設定します。 認証情報: Alibaba Cloud によって提供される AccessKey ID。 Resource Access Management (RAM) コンソールで AccessKey ID を表示できます。 AccessKey ペアの作成方法の詳細については、「AccessKey ペアを取得する」をご参照ください。 SignedHeaders: 署名計算に使用されるリクエストヘッダーの名前。セキュリティを強化するために、Authorization を除くすべての一般的なリクエストヘッダーを署名計算に使用するようお勧めします。 署名: 現在のリクエストの署名文字列です。詳細については、この Topic の「署名メソッド」セクションをご参照ください。 | 2023-10-26T10:22:32Z |
x-acs-security-token | 文字列 | セキュリティトークンサービス (STS) が認証に使用されている場合は「はい」 | STS トークン。このヘッダーを、[AssumeRole] 操作の応答の SecurityToken パラメーターの値に設定します。 |
リクエスト署名
API Gateway は、AccessKey ID と AccessKey シークレットを使用してリクエストに署名し、リクエストを認証します。 データ整合性とセキュリティを確保するために、API Gateway は HTTP または HTTPS リクエストごとに署名を計算し、リクエストに含まれる署名と比較して、呼び出し元の ID を認証します。
すべてのリクエストとレスポンスは UTF-8 でエンコードされます。
ステップ 1:正規化されたリクエストを作成する
次の擬似コードに基づいて、正規化されたリクエストを作成します。
CanonicalRequest =
HTTPRequestMethod + '\n' + // 大文字の HTTP リクエストメソッド。
CanonicalURI + '\n' + // 正規化された Uniform Resource Identifier(URI)。
CanonicalQueryString + '\n' + // 正規化されたクエリ文字列。
CanonicalHeaders + '\n' + // 正規化されたリクエストヘッダー。
SignedHeaders + '\n' + // 署名計算に使用されるリクエストヘッダー。
HashedRequestPayload // リクエスト本文のハッシュ値。
リクエストメソッド(HTTPRequestMethod)
GET や POST など、大文字の HTTP リクエストメソッド。
正規化された URI(CanonicalURI)
正規化された URI は、URL 内のエンコードされたリソースパスです。 リソースパスは、エンドポイントとクエリ文字列の間の部分です。 パスには、エンドポイントの後に続くスラッシュ(
/
)が含まれますが、クエリ文字列の前にある疑問符(?
)は含まれません。 署名計算には、正規化された URI を使用する必要があります。 正規化された URI を作成するには、RFC 3986 に基づいて、スラッシュ(/
)で区切られた文字列を UTF-8 でエンコードします。 エンコードルール:文字、数字、ハイフン(
-
)、アンダースコア(_
)、ピリオド(.
)、チルダ(~
)はエンコードする必要はありません。その他の文字は、
%
+ 16 進表記の文字の ASCII コードという形式でパーセントエンコードする必要があります。 たとえば、二重引用符("
)は%22
としてエンコードされます。 次の表に、エンコード前とエンコード後の一部の特殊文字を示します。 これらの特殊文字に注意してください。エンコード前
エンコード後
スペース文字()
%20
アスタリスク(
*
)%2A
%7E
チルダ(
~
)Java 標準ライブラリで
java.net.URLEncoder
を使用する場合は、標準ライブラリに基づいて文字列をエンコードします。 エンコードされた文字列では、プラス記号(+
)を%20
に、アスタリスク(*
)を%2A
に、%7E
をチルダ(~
)に置き換えます。 これにより、上記のエンコードルールに一致するエンコードされた文字列を取得できます。
重要API スタイルが RPC の場合は、スラッシュ(
/
)を CanonicalURI パラメーターの値として使用します。API スタイルが ROA の場合は、API 操作のメタデータにある
path
パラメーターの値をエンコードし、エンコードされた値を CanonicalURI パラメーターの値として使用します。 例:/api/v1/clusters
。正規化されたクエリ文字列(CanonicalQueryString)
API メタデータ で、API リクエストのリクエストパラメーターに
"in":"query"
位置情報が含まれている場合は、次の方法に基づいてリクエストパラメーターを連結する必要があります。すべてのリクエストパラメーターをパラメーター名でアルファベット順にソートします。
RFC 3986 に基づいて、パラメーター名と値を UTF-8 でエンコードします。 エンコードルールは、正規化された URI を作成するために使用されるルールと同じです。
等号(
=
)を使用して、各パラメーターのエンコードされた名前と値を連結します。 値のないパラメーターの場合は、空の文字列をパラメーター値として使用します。アンパサンド(
&
)を使用して、前のステップで取得した順序でエンコードされたパラメーターを連結します。
重要リクエストパラメーターが配列またはオブジェクトの場合は、パラメーターをマップに変換します。 たとえば、
{"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"}
に変換します。クエリ文字列が空の場合は、空の文字列を正規化されたクエリ文字列として使用します。
例:
ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai
HashedRequestPayload
ハッシュ関数を使用して RequestPayload を HashedRequestPayload に変換し、リクエストヘッダー 内の
x-acs-content-sha256
の値を HashedRequestPayload の値に変更します。 擬似コード:HashedRequestPayload = HexEncode(Hash(RequestPayload))
RequestPayload の値は、シナリオによって異なります。
API メタデータ で、リクエストパラメーターに
"in": "body"
または"in": "formData"
位置情報が含まれている場合は、リクエスト本文でパラメーターを指定し、RequestPayload の値をリクエスト本文の JSON 文字列に設定します。重要リクエストパラメーターに
"in": "formData"
位置情報が含まれている場合は、key1=value1&key2=value2&key3=value3
の形式でパラメーターを文字列に連結します。 リクエストパラメーターが配列またはオブジェクトの場合は、パラメーターをマップに変換します。 たとえば、{"key":["value1","value2"]}
を{"key.1":"value1","key.2":"value2"}
に変換します。 リクエストヘッダーにcontent-type=application/x-www-form-urlencoded
を追加します。リクエストパラメーターに
"in": "body"
位置情報が含まれている場合は、リクエストに Content-Type ヘッダーを追加します。 Content-Type ヘッダーの値は、リクエストコンテンツのタイプを指定します。 例:リクエストコンテンツが JSON 文字列の場合は、Content-Type ヘッダーの値を
application/json
に設定します。リクエストコンテンツがバイナリファイルストリームの場合は、Content-Type ヘッダーの値を
application/octet-stream
に設定します。
リクエストに本文がない場合は、RequestPayload の値を空の文字列に設定します。
Hash()はハッシュ関数を指定します。 SHA-256 アルゴリズムのみがサポートされています。
HexEncode()は、ハッシュ値を Base16 でエンコードします。 この関数は、16 進形式の小文字でエンコードされたハッシュ値を返します。
リクエスト本文が空の場合のサンプル値:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
正規化されたヘッダー(CanonicalHeaders)
次の方法に基づいて、リクエストヘッダー 内のパラメーターを連結します。
x-acs-
で始まるリクエストヘッダー、Host
ヘッダー、およびContent-Type
ヘッダーを除外します。ヘッダー名を小文字に変換し、ヘッダーをアルファベット順にソートします。
各ヘッダーの値の前後のスペースを削除します。
コロン(
:
)を使用してヘッダー名とヘッダー値を連結し、名前と値のペアの末尾に改行(\n
)を追加して、正規化されたヘッダーエントリを形成します。複数の正規化されたヘッダーエントリを文字列に連結します。
説明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 =
SignatureAlgorithm + '\n' +
HashedCanonicalRequest
SignatureAlgorithm
署名計算では、ACS3-HMAC-SHA256 アルゴリズムのみがサポートされています。
HashedCanonicalRequest
正規化されたリクエストのハッシュ値。 次の擬似コードは、ハッシュ値を生成する方法を示しています。
HashedCanonicalRequest = HexEncode(Hash(CanonicalRequest))
Hash()はハッシュ関数を指定します。 SHA-256 アルゴリズムのみがサポートされています。
HexEncode()は、ハッシュ値を Base16 でエンコードします。 この関数は、16 進形式の小文字でエンコードされたハッシュ値を返します。
例:
ACS3-HMAC-SHA256
7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259
ステップ 3:署名文字列を計算する
次の擬似コードに基づいて、署名文字列を計算します。
Signature = HexEncode(SignatureMethod(Secret, StringToSign))
StringToSign:ステップ 2 で作成された署名対象文字列。 値は UTF-8 でエンコードされます。
SignatureMethod:署名アルゴリズムとして HMAC-SHA256 を指定します。
Secret:AccessKey シークレット。
HexEncode:値を 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 は、複数のプログラミング言語と開発フレームワーク用の SDK を提供しています。API リクエストを開始し、リクエストの署名を自動的に生成するには、Alibaba Cloud SDK を使用することをお勧めします。署名を手動で計算する必要なく、Alibaba Cloud でアプリケーションを開発できます。
リクエストに署名する前に、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 |
操作固有のパラメーター
ImageId | win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd |
RegionId | cn-shanghai |
リクエストに署名するには、次の手順を実行します。
正規化リクエストを構築します。
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
署名対象文字列を構築します。
ACS3-HMAC-SHA256
7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259
署名文字列を計算します。
06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0
リクエストに署名文字列を追加します。
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 で署名方法を使用するには、次の Maven 依存関係を pom.xml ファイルに追加する必要があります。
<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.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.http.entity.ContentType;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 署名デモ
*/
public class SignatureDemo {
/**
* 日付のフォーマットに使用するツール。このツールは、DATETIME 文字列を yyyy-MM-dd'T'HH:mm:ss'Z' 形式の値にフォーマットできます。
*/
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
private static class SignatureRequest {
// HTTP メソッド
private final String httpMethod;
// 正規化 URI。リソースパスが空の場合は、スラッシュ (/) を CanonicalURI パラメーターの値として使用します。
private final String canonicalUri;
// エンドポイント
private final String host;
// API 名
private final String xAcsAction;
// API バージョン
private final String xAcsVersion;
// ヘッダー
TreeMap<String, String> headers = new TreeMap<>();
// ボディのパラメーターのバイト配列。API メタデータのリクエストパラメーターに "in":"body" または "in": "formData" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
byte[] body;
// クエリ文字列。API メタデータのリクエストパラメーターに "in":"query" の位置情報が含まれている場合は、リクエスト URL のパラメーターを連結します。
TreeMap<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);
SDF.setTimeZone(new SimpleTimeZone(0, "GMT")); // 日付フォーマットのタイムゾーンを GMT に設定します。
headers.put("x-acs-date", SDF.format(new Date()));
headers.put("x-acs-signature-nonce", UUID.randomUUID().toString());
}
}
/**
* System.getenv() は、AccessKey ID と AccessKey シークレットが環境変数から取得されることを指定します。
*/
private final static String ACCESS_KEY_ID = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
private final static String ACCESS_KEY_SECRET = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
/**
* 署名アルゴリズム。
*/
private static final String ALGORITHM = "ACS3-HMAC-SHA256";
/**
* 署名のサンプル。 main() メソッドのパラメーターを調整する必要があります。
* ROA の API 操作と RPC スタイルの API 操作は、canonicalUri 値のロジックのみが異なります。
* <p>
* リクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーター型 (type)、およびリクエストパラメーターの位置 (in) を取得し、署名リクエスト (SignatureRequest) に情報をカプセル化します。
*1. API メタデータのリクエストパラメーターに "in":"query" の位置情報が含まれている場合は、クエリ文字列 (queryParam) のパラメーターを指定します。
*2. API メタデータのリクエストパラメーターに "in": "body" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
*3. API メタデータのリクエストパラメーターに "in": "formData" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
*/
public static void main(String[] args) throws IOException {
// 例 1: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in":"query")
String httpMethod = "POST"; // リクエストメソッド。API メタデータから取得できます。POST メソッドを使用することをお勧めします。
String canonicalUri = "/"; // RPC API 操作のリソースパスは空です。したがって、スラッシュ (/) が CanonicalURI パラメーターの値として使用されます。
String host = "ecs.cn-hangzhou.aliyuncs.com"; // クラウドサービスのエンドポイント。
String xAcsAction = "DescribeInstanceStatus"; // 実行する API 操作。
String xAcsVersion = "2014-05-26"; // API のバージョン番号。
SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
// DescribeInstanceStatus 操作を呼び出すためのリクエストパラメーター:
// RegionId は API メタデータで文字列として定義されています。必須パラメーターであり、位置は "in":"query" です。
signatureRequest.queryParam.put("RegionId", "cn-hangzhou");
// InstanceId は API メタデータで文字列として定義されています。オプションパラメーターであり、位置は "in":"query" です。
String[] instanceIds = {"i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"};
signatureRequest.queryParam.put("InstanceId", Arrays.asList(instanceIds));
/*// 例 2: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in":"body")
String httpMethod = "POST";
String canonicalUri = "/";
String host = "ocr-api.cn-hangzhou.aliyuncs.com";
String xAcsAction = "RecognizeGeneral";
String xAcsVersion = "2021-07-07";
SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
// API メタデータのリクエストパラメーターに "in":"body" の位置情報が含まれている場合は、ボディのリクエストパラメーターを指定します。
signatureRequest.body = Files.readAllBytes(Paths.get("D:\\test.png"));
signatureRequest.headers.put("content-type", "application/octet-stream");*/
/*// 例 3: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in": "formData")
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);
// TranslateGeneral 操作を呼び出すためのリクエストパラメーター:
// Context は API メタデータで文字列として定義されています。オプションパラメーターであり、位置は "in":"query" です。
signatureRequest.queryParam.put("Context", "Morning");
// FormatType、SourceLanguage、および TargetLanguage パラメーターのメタデータの位置は "in":"formData" として定義されています。
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 = formDataToString(body);
signatureRequest.body = formDataToString.getBytes(StandardCharsets.UTF_8);
signatureRequest.headers.put("content-type", "application/x-www-form-urlencoded");*/
/*// ROA スタイルの API 操作の POST リクエストを構築します。
String httpMethod = "POST";
String canonicalUri = "/clusters"; // API メタデータで正規化 URI を取得します。この場合、正規化 URI は "path": "/clusters" です。
String host = "cs.cn-beijing.aliyuncs.com"; // エンドポイント
String xAcsAction= "CreateCluster"; // 実行する API 操作。
String xAcsVersion= "2015-12-15"; // API のバージョン番号。
SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
// API 操作の呼び出しに必要なパラメーター。メタデータのリクエストパラメーターに "in": "body" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
TreeMap<String, Object> body = new TreeMap<>();
body.put("name", "Test");
body.put("region_id", "cn-beijing");
body.put("cluster_type", "ExternalKubernetes");
body.put("vpcid", "vpc-2zeou1uod4ylaXXXXXXXX");
body.put("container_cidr","10.0.0.0/8");
body.put("service_cidr", "10.2.0.0/24");
body.put("security_group_id", "sg-2ze1a0rlgeo7XXXXXXXX");
body.put("vswitch_ids", Collections.singletonList(
"vsw-2zei30dhfldu8XXXXXXXX"
));
Gson gson = (new GsonBuilder()).disableHtmlEscaping().create();
signatureRequest.body = gson.toJson(body).getBytes(StandardCharsets.UTF_8);
signatureRequest.headers.put("content-type", "application/json");*/
/*// ROA スタイルの API 操作の GET リクエストを構築します。
String httpMethod = "GET";
// canonicalUri パラメーターの値にパスパラメーターの値が含まれている場合は、percentCode({パスパラメーターの値}) を指定してパスパラメーターの値をエンコードします。
String canonicalUri = "/clusters/" + percentCode("cdb14b4f85130407da748fd3fXXXXXXXX") + "/resources";
String host = "cs.cn-beijing.aliyuncs.com"; // エンドポイント
String xAcsAction = "DescribeClusterResources"; // 実行する API 操作。
String xAcsVersion = "2015-12-15"; // API のバージョン番号。
SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
signatureRequest.queryParam.put("with_addon_resources", true);*/
/*// ROA API 操作の DELETE リクエストを構築します。
String httpMethod = "DELETE";
String canonicalUri = "/clusters/" + percentCode("cdb14b4f85130407da748fd3fXXXXXXXX");
String host = "cs.cn-beijing.aliyuncs.com";
String xAcsAction = "DeleteCluster";
String xAcsVersion = "2015-12-15";
SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);*/
// リクエストに署名します。
getAuthorization(signatureRequest);
// API 操作を呼び出します。
callApi(signatureRequest);
}
private static void callApi(SignatureRequest signatureRequest) {
try {
// HttpClient を使用してリクエストを送信します。
String url = "https://" + signatureRequest.host + signatureRequest.canonicalUri;
URIBuilder uriBuilder = new URIBuilder(url);
// リクエストパラメーターを指定します。
for (Map.Entry<String, Object> entry : signatureRequest.queryParam.entrySet()) {
uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue()));
}
System.out.println(uriBuilder.build());
HttpUriRequest httpRequest;
switch (signatureRequest.httpMethod) {
case "GET":
httpRequest = new HttpGet(uriBuilder.build());
break;
case "POST":
HttpPost httpPost = new HttpPost(uriBuilder.build());
if (signatureRequest.body != null) {
httpPost.setEntity(new ByteArrayEntity(signatureRequest.body, ContentType.create(signatureRequest.headers.get("content-type"))));
}
httpRequest = httpPost;
break;
case "DELETE":
httpRequest = new HttpDelete(uriBuilder.build());
break;
default:
System.out.println("サポートされていない HTTP メソッド: " + signatureRequest.httpMethod);
throw new IllegalArgumentException("サポートされていない HTTP メソッド");
}
// HTTP リクエストヘッダーを指定します。
for (Map.Entry<String, String> entry : signatureRequest.headers.entrySet()) {
httpRequest.addHeader(entry.getKey(), String.valueOf(entry.getValue()));
}
// リクエストを送信します。
try (CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(httpRequest)) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println(result);
} catch (IOException e) {
// エラーを処理します。
System.out.println("リクエストの送信に失敗しました");
e.printStackTrace();
}
} catch (URISyntaxException e) {
// エラーを処理します。
System.out.println("無効な URI 構文");
e.printStackTrace();
}
}
/**
* このメソッドは、指定された HTTP リクエストメソッド、正規化 URI、およびクエリパラメーターに基づいて署名文字列を計算および生成するために使用されます。
*/
private static void getAuthorization(SignatureRequest signatureRequest) {
try {
// List 型と Map 型のクエリパラメーターをフラット化します。
TreeMap<String, Object> newQueryParam = new TreeMap<>();
processObject(newQueryParam, "", signatureRequest.queryParam);
signatureRequest.queryParam = newQueryParam;
// ステップ 1: 正規化リクエストを構築します。
// リクエストパラメーター。クエリ文字列が空の場合は、空の文字列を正規化クエリ文字列として使用します。
StringBuilder canonicalQueryString = new StringBuilder();
signatureRequest.queryParam.entrySet().stream().map(entry -> percentCode(entry.getKey()) + "="
+ percentCode(String.valueOf(entry.getValue()))).forEachOrdered(queryPart -> {
// 正規化クエリ文字列が空でない場合は、アンパサンド (&) を使用して複数のリクエストパラメーターを連結します。
if (canonicalQueryString.length() > 0) {
canonicalQueryString.append("&");
}
canonicalQueryString.append(queryPart);
});
// リクエストボディのハッシュ値を計算します。
String requestPayload = ""; // リクエストボディ。GET リクエストのようにリクエストボディが空の場合は、空の文字列を RequestPayload パラメーターの値として使用します。
String hashedRequestPayload = signatureRequest.body != null ? sha256Hex(signatureRequest.body) : sha256Hex(requestPayload.getBytes(StandardCharsets.UTF_8));
signatureRequest.headers.put("x-acs-content-sha256", hashedRequestPayload);
// リクエストヘッダーを構築します。複数の正規化リクエストヘッダーを、小文字のヘッダー名でアルファベット順に連結します。
StringBuilder canonicalHeaders = new StringBuilder();
// 署名計算に使用するリクエストヘッダー。小文字のヘッダー名でアルファベット順にすべてのヘッダーをセミコロン (;) で連結します。
StringBuilder signedHeadersSb = new StringBuilder();
signatureRequest.headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") || "host".equalsIgnoreCase(entry.getKey()) || "content-type".equalsIgnoreCase(entry.getKey())).sorted(Map.Entry.comparingByKey()).forEach(entry -> {
String lowerKey = entry.getKey().toLowerCase();
String value = String.valueOf(entry.getValue()).trim();
canonicalHeaders.append(lowerKey).append(":").append(value).append("\n");
signedHeadersSb.append(lowerKey).append(";");
});
String signedHeaders = signedHeadersSb.substring(0, signedHeadersSb.length() - 1);
String canonicalRequest = signatureRequest.httpMethod + "\n" + signatureRequest.canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + 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(ACCESS_KEY_SECRET.getBytes(StandardCharsets.UTF_8), stringToSign)).toLowerCase();
System.out.println("signature=========>" + signature);
// ステップ 4: Authorization ヘッダーを指定します。
String authorization = ALGORITHM + " " + "Credential=" + ACCESS_KEY_ID + ",SignedHeaders=" + signedHeaders + ",Signature=" + signature;
System.out.println("authorization=========>" + authorization);
signatureRequest.headers.put("Authorization", authorization);
} catch (Exception e) {
// エラーを処理します。
System.out.println("認証の取得に失敗しました");
e.printStackTrace();
}
}
/**
* formData 型のパラメーターを処理します。
*
* @param formData formData 型のパラメーター。
* @return String
*/
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();
}
/**
* 再帰が実行されるオブジェクト。マップやリストなどの複合オブジェクトは、キーと値のペアに再帰的に分解されます。
*
* @param map キーと値のペアを格納する元のマップ。マップは再帰的に更新されます。
* @param key 処理するキー。再帰が深くなるにつれて、キーは特定の値へのパスを累積します。
* @param value キーの値。値は、Map 型または List 型にすることができます。
*/
private static void processObject(Map<String, Object> map, String key, Object value) {
// null 値の場合は、それ以上の処理は必要ありません。
if (value == null) {
return;
}
if (key == null) {
key = "";
}
// 値が List 型の場合は、リストをトラバースし、各要素に対して再帰を実行します。
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 型の場合は、マップをトラバースし、各キーと値のペアに対して再帰を実行します。
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);
}
// 値が byte[] 形式の場合は、値を UTF-8 でエンコードされた文字列に変換します。
if (value instanceof byte[]) {
map.put(key, new String((byte[]) value, StandardCharsets.UTF_8));
} else {
// 他の型の値を文字列に変換します。
map.put(key, String.valueOf(value));
}
}
}
/**
* HMAC-SHA256 アルゴリズムを使用して、メッセージ認証コード (MAC) を生成します。
*
* @param secretKey MAC の生成に使用するキー。キーは機密にする必要があります。
* @param str 認証するメッセージ。
* @return HMAC-SHA256 アルゴリズムを使用して計算された MAC。
* @throws Exception MAC ジェネレーターの初期化時または MAC の計算時に報告されるエラー。
*/
public static byte[] hmac256(byte[] secretKey, String str) throws Exception {
// HMAC-SHA256 アルゴリズムが実装されているオブジェクトを取得します。
Mac mac = Mac.getInstance("HmacSHA256");
// MAC ジェネレーターを初期化するためのキー標準を作成します。
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, mac.getAlgorithm());
// MAC ジェネレーターを初期化します。
mac.init(secretKeySpec);
// MAC を計算して返します。
return mac.doFinal(str.getBytes(StandardCharsets.UTF_8));
}
/**
* SHA-256 アルゴリズムを使用して署名文字列のハッシュ値を計算し、ハッシュ値を 16 進数形式の文字列として返します。
*
* @param input SHA256 アルゴリズムを使用して計算するバイト配列。
* @return 小文字の 16 進数形式の文字列。
* @throws Exception SHA-256 アルゴリズムが実装されているオブジェクトの取得時に報告されるエラー。
*/
public static String sha256Hex(byte[] input) throws Exception {
// SHA-256 アルゴリズムが実装されているオブジェクトを取得します。
MessageDigest md = MessageDigest.getInstance("SHA-256");
// SHA-256 アルゴリズムを使用して文字列のハッシュ値を計算します。
byte[] d = md.digest(input);
// ハッシュ値を小文字の 16 進数形式の文字列に変換して返します。
return DatatypeConverter.printHexBinary(d).toLowerCase();
}
/**
* 指定された文字列に対して URL エンコーディングを実行します。
* 文字列を UTF-8 でエンコードし、特定の文字を置き換えて URL エンコーディング仕様に準拠させます。
*
* @param str URL エンコードする文字列。
* @return URL エンコードされた文字列。プラス記号 (+) は %20 に、アスタリスク (*) は %2A に、%7E はチルダ (~) に置き換えられます。
*/
public static String percentCode(String str) {
if (str == null) {
throw new IllegalArgumentException("指定された文字列を null にすることはできません。");
}
try {
return URLEncoder.encode(str, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 エンコーディングはサポートされていません。", e);
}
}
}
Python
この例では、Python 3.12.3 ランタイム環境を使用しています。ビジネス要件に基づいてパラメーターを調整してください。
pytz ライブラリと requests ライブラリを手動でインストールする必要があります。Python のバージョンに基づいて、ターミナルで次のコマンドを実行します。
Python3
pip3 install pytz
pip3 install requests
import hashlib
import hmac
import json
import os
import uuid
from collections import OrderedDict
from urllib.parse import urlencode, quote_plus
import pytz
import requests
from datetime import datetime
class SignatureRequest:
def __init__(self, http_method, canonical_uri, host, x_acs_action, x_acs_version):
self.http_method = http_method
self.canonical_uri = canonical_uri
self.host = host
self.x_acs_action = x_acs_action
self.x_acs_version = x_acs_version
self.headers = self._init_headers()
self.query_param = OrderedDict()
self.body = None
def _init_headers(self):
headers = OrderedDict()
headers['host'] = self.host
headers['x-acs-action'] = self.x_acs_action
headers['x-acs-version'] = self.x_acs_version
current_time = datetime.now(pytz.timezone('Etc/GMT'))
headers['x-acs-date'] = current_time.strftime('%Y-%m-%dT%H:%M:%SZ')
headers['x-acs-signature-nonce'] = str(uuid.uuid4())
return headers
def sorted_query_params(self):
# クエリパラメーターをパラメーター名でアルファベット順にソートし、エンコードされた文字列を返します。
sorted_query_params = sorted(self.query_param.items(), key=lambda item: item[0])
self.query_param = {k: v for k, v in sorted_query_params}
def sorted_headers(self):
# リクエストヘッダーをヘッダー名でアルファベット順にソートし、エンコードされた文字列を返します。
sorted_headers = sorted(self.headers.items(), key=lambda item: item[0])
self.headers = {k: v for k, v in sorted_headers}
def get_authorization(request):
try:
new_query_param = OrderedDict()
process_object(new_query_param, '', request.query_param)
request.query_param = new_query_param
# ステップ 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 ''.encode('utf-8'))
request.headers['x-acs-content-sha256'] = hashed_request_payload
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(k for k in 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()
print(signature)
# ステップ 4: Authorization ヘッダーを指定します。
authorization = f'{ALGORITHM} Credential={ACCESS_KEY_ID},SignedHeaders={signed_headers},Signature={signature}'
print(authorization)
request.headers['Authorization'] = authorization
except Exception as e:
print("認証の取得に失敗しました")
print(e)
def form_data_to_string(form_data):
tile_map = OrderedDict()
process_object(tile_map, '', form_data)
return urlencode(tile_map)
def process_object(result_map, key, value):
# null 値の場合は、それ以上の処理は必要ありません。
if value is None:
return
if key is None:
key = ""
# 値がリスト型の場合は、リストをトラバースし、各要素に対して再帰を実行します。
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:
# キーがピリオド (.) で始まる場合は、ピリオド (.) を削除してキーの連続性を維持します。
if key.startswith("."):
key = key[1:]
# 値が byte[] 形式の場合は、値を UTF-8 でエンコードされた文字列に変換します。
if isinstance(value, bytes):
result_map[key] = value.decode('utf-8')
else:
# 他の型の値を文字列に変換します。
result_map[key] = str(value)
def hmac256(key, msg):
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
def sha256_hex(s):
return hashlib.sha256(s).hexdigest()
def call_api(request):
url = f'https://{request.host}{request.canonical_uri}'
if request.query_param:
url += '?' + urlencode(request.query_param, doseq=True, safe='*')
print(url)
headers = {k: v for k, v in request.headers.items()}
if request.body:
data = request.body
else:
data = None
try:
response = requests.request(method=request.http_method, url=url, headers=headers, data=data)
response.raise_for_status()
print(response.text)
except requests.RequestException as e:
print("リクエストの送信に失敗しました")
print(e)
def percent_code(encoded_str):
return encoded_str.replace('+', '%20').replace('*', '%2A').replace('%7E', '~')
# 環境変数から AccessKey ID と AccessKey シークレットを取得します。
ACCESS_KEY_ID = os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID')
ACCESS_KEY_SECRET = os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET')
ALGORITHM = 'ACS3-HMAC-SHA256'
"""
署名のサンプル。 main() メソッドのパラメーターを調整する必要があります。
ROA の API 操作と RPC スタイルの API 操作は、canonicalUri 値のロジックのみが異なります。
リクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーター型 (type)、およびリクエストパラメーターの位置 (in) を取得し、署名リクエスト (SignatureRequest) に情報をカプセル化します。
1. API メタデータのリクエストパラメーターに "in":"query" の位置情報が含まれている場合は、クエリ文字列 (queryParam) のパラメーターを指定します。
2. API メタデータのリクエストパラメーターに "in": "body" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
3. API メタデータのリクエストパラメーターに "in": "formData" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
"""
if __name__ == "__main__":
# 例 1: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in":"query")
http_method = "POST" # リクエストメソッド。API メタデータから取得できます。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 は API メタデータで文字列として定義されています。必須パラメーターであり、位置は "in":"query" です。
signature_request.query_param['RegionId'] = 'cn-hangzhou'
# InstanceId は API メタデータで文字列として定義されています。オプションパラメーターであり、位置は "in":"query" です。
signature_request.query_param['InstanceId'] = ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"]
# # 例 2: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in":"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)
# # API メタデータのリクエストパラメーターに "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"
# # 例 3: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in": "formData")
# 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 は API メタデータで文字列として定義されています。オプションパラメーターであり、位置は "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"
# # 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)
# # API メタデータのリクエストパラメーターに "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"
# # ROA スタイルの API 操作の GET リクエストを構築します。
# http_method = "GET"
# # canonicalUri パラメーターの値にパスパラメーターの値が含まれている場合は、percent_code({パスパラメーターの値}) を指定してパスパラメーターの値をエンコードします。
# 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
# # ROA スタイルの API 操作の GET リクエストを構築します。
# http_method = "DELETE"
# # canonicalUri パラメーターの値にパスパラメーターの値が含まれている場合は、percent_code({パスパラメーターの値}) を指定してパスパラメーターの値をエンコードします。
# 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)
signature_request.sorted_query_params()
get_authorization(signature_request)
# API 操作を呼び出します。
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 シークレットが環境変数から取得されることを指定します。
var (
AccessKeyId = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
AccessKeySecret = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
ALGORITHM = "ACS3-HMAC-SHA256"
)
// 署名のサンプル。 main() メソッドのパラメーターを調整する必要があります。
// ROA の API 操作と RPC スタイルの API 操作は、canonicalUri 値のロジックのみが異なります。
// リクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーター型 (type)、およびリクエストパラメーターの位置 (in) を取得し、署名リクエスト (SignatureRequest) に情報をカプセル化します。
// 1. API メタデータのリクエストパラメーターに "in":"query" の位置情報が含まれている場合は、クエリ文字列 (queryParam) のパラメーターを指定します。
// 2. API メタデータのリクエストパラメーターに "in": "body" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
// 3. API メタデータのリクエストパラメーターに "in": "body" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
func main() {
// 例 1: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in":"query")
httpMethod := "POST" // HTTP リクエストメソッド。ほとんどの場合、POST または GET メソッドを使用して RPC スタイルの API 操作を呼び出すことができます。この例では、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 は API メタデータで文字列として定義されています。必須パラメーターであり、位置は "in":"query" です。
req.queryParam["RegionId"] = "cn-hangzhou"
// InstanceId は API メタデータで文字列として定義されています。オプションパラメーターであり、位置は "in":"query" です。
instanceIds := []interface{}{"i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"}
req.queryParam["InstanceId"] = instanceIds
// // 例 2: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in":"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("ファイルの読み取りエラー:", err)
// return
// }
// req.body = bytes
// req.headers["content-type"] = "application/octet-stream"
// // 例 3: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in": "formData")
// httpMethod := "POST"
// canonicalUri := "/"
// host := "mt.aliyuncs.com"
// xAcsAction := "TranslateGeneral"
// xAcsVersion := "2018-10-12"
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
// // TranslateGeneral 操作を呼び出すためのリクエストパラメーター:
// // Context は API メタデータで文字列として定義されています。オプションパラメーターであり、位置は "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("JSON へのマーシャリングエラー:", err)
// return
// }
// req.body = []byte(jsonBytes)
// req.headers["content-type"] = "application/json; charset=utf-8"
// // ROA API 操作の GET リクエストを構築します。
// httpMethod := "GET"
// // canonicalUri パラメーターの値にパスパラメーターの値が含まれている場合は、percentCode({パスパラメーターの値}) を指定してパスパラメーターの値をエンコードします。
// 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({パスパラメーターの値}) を指定してパスパラメーターの値をエンコードします。
// 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) {
// 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
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)
}
signature := strings.ToLower(hex.EncodeToString(byteData))
// ステップ 4: Authorization ヘッダーを指定します。
authorization := ALGORITHM + " Credential=" + AccessKeyId + ",SignedHeaders=" + signedHeaders + ",Signature=" + signature
fmt.Printf("authorization========>%s\n", authorization)
req.headers["Authorization"] = authorization
}
func hmac256(key []byte, toSignString string) ([]byte, error) {
// HMAC-SHA256 アルゴリズムが実装されているオブジェクトを取得します。
h := hmac.New(sha256.New, key)
// 署名対象文字列を構築します。
_, err := h.Write([]byte(toSignString))
if err != nil {
return nil, err
}
// 署名文字列のハッシュ値を計算して返します。
return h.Sum(nil), nil
}
func sha256Hex(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: 再帰が実行されるオブジェクト。マップやリストなどの複合オブジェクトは、キーと値のペアに再帰的に分解されます。
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 ランタイム環境を使用しています。ビジネス要件に基づいてパラメーターを調整してください。
この例では、JavaScript を使用しています。
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 encoder = new TextEncoder()
if (!accessKeyId || !accessKeySecret) {
console.error('ALIBABA_CLOUD_ACCESS_KEY_ID と ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数を設定する必要があります。');
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;
// すべてのヘッダー名を小文字に変換します。
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('認証の取得に失敗しました');
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('リクエストの送信に失敗しました:', 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() メソッドのパラメーターを調整する必要があります。
* ROA の API 操作と RPC スタイルの API 操作は、canonicalUri 値のロジックのみが異なります。
*
* リクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーター型 (type)、およびリクエストパラメーターの位置 (in) を取得し、署名リクエスト (SignatureRequest) に情報をカプセル化します。
*1. API メタデータのリクエストパラメーターに "in":"query" の位置情報が含まれている場合は、クエリ文字列 (queryParam) のパラメーターを指定します。
*2. API メタデータのリクエストパラメーターに "in": "body" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
*3. API メタデータのリクエストパラメーターに "in": "formData" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
*/
// 例 1: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in":"query")
const httpMethod = 'POST'; // HTTP リクエストメソッド。ほとんどの場合、POST または GET メソッドを使用して RPC スタイルの API 操作を呼び出すことができます。この例では、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 は API メタデータで文字列として定義されています。必須パラメーターであり、位置は "in":"query" です。
RegionId: 'cn-hangzhou',
// InstanceId は API メタデータで文字列として定義されています。オプションパラメーターであり、位置は "in":"query" です。
InstanceId: ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"],
}
// // 例 2: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in":"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);
// // API メタデータのリクエストパラメーターに "in": "body" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
// signRequest.body = bytes;
// signRequest.headers['content-type'] = 'application/octet-stream';
// // 例 3: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in": "formData")
// const httpMethod = 'POST'; // HTTP リクエストメソッド。ほとんどの場合、POST または GET メソッドを使用して RPC スタイルの API 操作を呼び出すことができます。この例では、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 は API メタデータで文字列として定義されています。オプションパラメーターであり、位置は "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);
// // API メタデータのリクエストパラメーターに "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({パスパラメーターの値}) を指定してパスパラメーターの値をエンコードします。
// 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({パスパラメーターの値}) を指定してパスパラメーターの値をエンコードします。
// 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;
public function __construct()
{
date_default_timezone_set('UTC'); // タイムゾーンを GMT に設定します。
$this->AccessKeyId = getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'); // getenv() は、RAM ユーザーの AccessKey ID が環境変数から取得されることを指定します。
$this->AccessKeySecret = getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'); // getenv() は、RAM ユーザーの AccessKey シークレットが環境変数から取得されることを指定します。
$this->ALGORITHM = 'ACS3-HMAC-SHA256'; // 暗号化方法を指定します。
}
/**
* 署名のサンプル。 main() メソッドのパラメーターを調整する必要があります。
* ROA の API 操作と RPC スタイルの API 操作は、canonicalUri 値のロジックのみが異なります。
*
* リクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーター型 (type)、およびリクエストパラメーターの位置 (in) を取得し、署名リクエスト (SignatureRequest) に情報をカプセル化します。
*1. API メタデータのリクエストパラメーターに "in":"query" の位置情報が含まれている場合は、クエリ文字列 (queryParam) のパラメーターを指定します。
*2. API メタデータのリクエストパラメーターに "in": "body" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
*3. API メタデータのリクエストパラメーターに "in": "formData" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
*/
public function main()
{
// 例 1: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in":"query")
$request = $this->createRequest('POST', '/', 'ecs.cn-hangzhou.aliyuncs.com', 'DescribeInstanceStatus', '2014-05-26');
// DescribeInstanceStatus 操作を呼び出すためのリクエストパラメーター:
$request['queryParam'] = [
// RegionId は API メタデータで文字列として定義されています。必須パラメーターであり、位置は "in":"query" です。
'RegionId' => 'cn-hangzhou',
// InstanceId は API メタデータで文字列として定義されています。オプションパラメーターであり、位置は "in":"query" です。
'InstanceId' => ["i-bp11ht4h2kdXXXXXXXX", "i-bp16maz3h3xgXXXXXXXX", "i-bp10r67hmslXXXXXXXX"]
];
// // 例 2: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in":"body")
// $request = $this->createRequest('POST', '/', 'ocr-api.cn-hangzhou.aliyuncs.com', 'RecognizeGeneral', '2021-07-07');
// // API メタデータのリクエストパラメーターに "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);
// // 例 3: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in": "formData")
// $request = $this->createRequest('POST', '/', 'mt.aliyuncs.com', 'TranslateGeneral', '2018-10-12');
// // TranslateGeneral 操作を呼び出すためのリクエストパラメーター:
// $request['queryParam'] = [
// // Context は API メタデータで文字列として定義されています。オプションパラメーターであり、位置は "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({パスパラメーターの値}) を指定してパスパラメーターの値をエンコードします。
// $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;
$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 証明書の検証を無効にします。これにより、セキュリティリスクが高まります。本番環境では、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 "サポートされていない HTTP メソッド: " . $request['body'];
throw new Exception("サポートされていない HTTP メソッド");
}
// リクエストを送信します。
$result = curl_exec($ch);
// エラーが発生したかどうかを確認します。
if (curl_errno($ch)) {
echo "リクエストの送信に失敗しました: " . curl_error($ch);
} else {
echo $result;
}
} catch (Exception $e) {
echo "エラー: " . $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 = new SortedDictionary<string, object>();
QueryParam = new Dictionary<string, object>();
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("環境変数 ALIBABA_CLOUD_ACCESS_KEY_ID が指定されていません。");
private static readonly string AccessKeySecret = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET") ?? throw new InvalidOperationException("環境変数 ALIBABA_CLOUD_ACCESS_KEY_SECRET が指定されていません。");
private const string Algorithm = "ACS3-HMAC-SHA256";
private const string ContentType = "content-type";
/**
* 署名のサンプル。 main() メソッドのパラメーターを調整する必要があります。
* ROA の API 操作と RPC スタイルの API 操作は、canonicalUri 値のロジックのみが異なります。
*
* リクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーター型 (type)、およびリクエストパラメーターの位置 (in) を取得し、署名リクエスト (SignatureRequest) に情報をカプセル化します。
*1. API メタデータのリクエストパラメーターに "in":"query" の位置情報が含まれている場合は、クエリ文字列 (queryParam) のパラメーターを指定します。
*2. API メタデータのリクエストパラメーターに "in": "body" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
*3. API メタデータのリクエストパラメーターに "in": "formData" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
*/
public static void Main(string[] args)
{
// 例 1: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in":"query")
string httpMethod = "POST"; // HTTP リクエストメソッド。ほとんどの場合、POST または GET メソッドを使用して RPC スタイルの API 操作を呼び出すことができます。この例では、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 は API メタデータで文字列として定義されています。必須パラメーターであり、位置は "in":"query" です。
request.QueryParam["RegionId"] = "cn-hangzhou";
// InstanceId は API メタデータで文字列として定義されています。オプションパラメーターであり、位置は "in":"query" です。
List<string> instanceIds = new List<string> { "i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX" };
request.QueryParam["InstanceId"] = instanceIds;
// // 例 2: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in":"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);
// // API メタデータのリクエストパラメーターに "in": "body" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
// request.Body = File.ReadAllBytes(@"D:\test.png");
// request.Headers["content-type"] = "application/octet-stream";
// // 例 3: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in": "formData")
// 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 は API メタデータで文字列として定義されています。オプションパラメーターであり、位置は "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({パスパラメーターの値}) を指定してパスパラメーターの値をエンコードします。
// 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({パスパラメーターの値}) を指定してパスパラメーターの値をエンコードします。
// 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) // 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("無効な URI 構文");
Console.WriteLine(e.Message);
return null;
}
catch (Exception e)
{
Console.WriteLine("リクエストの送信に失敗しました");
Console.WriteLine(e);
return null;
}
}
private static void GetAuthorization(Request request)
{
try
{
// 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 == null ? Encoding.UTF8.GetBytes("") : request.Body;
string hashedRequestPayload = Sha256Hash(requestPayload);
request.Headers["x-acs-content-sha256"] = hashedRequestPayload;
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("認証の取得に失敗しました");
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("指定された文字列を null または空にすることはできません");
}
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; // base64 クレートライブラリを読み込みます。
/// 現在のタイムスタンプを取得します。単位: 秒。
pub fn current_timestamp() -> Result<u64, SystemTimeError> {
Ok(SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs())
}
// 指定された文字列に対して URL エンコーディングを実行します。戻り値の型を &str で識別される文字列スライスに指定します: URLEncoder.encode(str, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~")。
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) // Cow<str> を返します。これは、文字列または &str で識別される文字列スライスにすることができます。
}
/// SHA256 アルゴリズムを使用して値を計算します。
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!("sha256 でデータキーを使用できませんでした:{}", 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"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let mut rng = rand::thread_rng();
(0..length)
.map(|_| CHARSET[rng.gen_range(0..CHARSET.len())] as char)
.collect()
}
/// ランダムな文字列を number used once (nonce) として生成します。
pub fn generate_nonce() -> String {
generate_random_string(32)
}
// FormData データ型を指定します。
#[derive(Debug, Clone)]
pub enum FormValue {
String(String),
// データ型を追加します (例: Vec<String>、HashSet<String>、HashMap<String, String>)。
Vec(Vec<String>),
HashMap(HashMap<String, String>),
}
// リクエスト内のさまざまなボディタイプ (JSON、マップ、バイナリボディなど) を管理するため、一連のボディタイプを列挙します。
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)], // クエリパラメーターを $query_params 変数に追加します。
action: &str,
version: &str,
body: RequestBody, // ボディパラメーターを受信するように指定します。ボディタイプには、JSON、マップ、バイナリがあります。
access_key_id: &str,
access_key_secret: &str,
) -> Result<String, String> {
// CanonicalQueryString: 正規化クエリ文字列を構築します。
let canonical_query_string = build_sored_encoded_query_string(query_params);
// ボディコンテンツを処理し、ボディタイプを決定します。
let body_content = match &body { // ボディを参照して、ボディの所有権が変更されないようにします。
RequestBody::Json(body_map) => json!(body_map).to_string(), // ボディがマップの場合は、ボディを &str で識別される文字列スライスに変換し、文字列スライスを body_content 変数に格納します。
RequestBody::Binary(_) => String::new(), // ボディがバイナリの場合は、空の文字列を指定し、body_content 変数を空のままにします。
RequestBody::FormData(form_data) => {
let params: Vec<String> = form_data
.iter()
.flat_map(|(k, v)| {
match v {
FormValue::String(s) => {
// FormValue が String に設定されている場合:
vec![format!("{}={}", percent_code(k), percent_code(&s))]
},
FormValue::Vec(vec) => {
// FormValue が Vec に設定されている場合:
vec.iter()
.map(|s| format!("{}={}", percent_code(k), percent_code(s)))
.collect::<Vec<_>>()
},
FormValue::HashMap(map) => {
// FormValue が HashMap に設定されている場合:
map.iter()
.map(|(sk, sv)| format!("{}={}", percent_code(sk), percent_code(sv)))
.collect::<Vec<_>>()
},
}
})
.collect();
params.join("&") // key=value&key=value の形式で値を指定します。
},
RequestBody::None => String::new(),
};
// ボディとクエリパラメーターを出力します。
println!("リクエストボディ: {}", body_content);
println!("クエリパラメーター: {:?}", query_params);
let hashed_request_payload = sha256_hex(&body_content);
// x-acs-date
let now_time = current_timestamp().map_err(|e| format!("現在のタイムスタンプの取得に失敗しました: {}", e))?;
let datetime = DateTime::from_timestamp(now_time as i64, 0).ok_or_else(|| format!("タイムスタンプから日付時刻を取得できませんでした: {}", now_time))?;
let datetime_str = datetime.format("%Y-%m-%dT%H:%M:%SZ").to_string();
// x-acs-signature-nonce
let signature_nonce = generate_nonce();
// リクエストヘッダーを構築します。
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());
// 署名計算に使用するヘッダー。
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(";");
// 正規化リクエストヘッダー。
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
);
// 署名対象文字列を計算します。
let result = sha256_hex(&canonical_request);
let string_to_sign = format!("ACS3-HMAC-SHA256\n{}", result);
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
);
headers.insert("Authorization", HeaderValue::from_str(&auth_data).unwrap());
// リクエストを送信します。
let url = format!("https://{}{}", host, canonical_uri);
let response = send_request(
&client,
method,
&url,
headers,
query_params, // クエリパラメーターを受信します。
&body, // ボディパラメーターを受信します。ボディタイプには、JSON、マップ、バイナリがあります。
&body_content, // ボディパラメーターを受信します。ボディがマップ型または formDate 型の場合は、この変数に値が設定されます。ボディがバイナリ型の場合は、この変数は空です。
)
.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, // ボディパラメーターを受信します。ボディタイプには、JSON、マップ、バイナリがあります。
body_content: &str, // ボディパラメーターを受信します。ボディがマップ型または formDate 型の場合は、この変数に値が設定されます。ボディがバイナリ型の場合は、この変数は空です。
) -> Result<Response, String> {
let mut request_builder = client.request(method, url);
// クエリパラメーターを追加します。
if !query_params.is_empty() {
// この例では、query_params を使用してクエリパラメーターを指定します。
request_builder = request_builder.query(query_params);
}
// リクエストヘッダーを追加します。
for (k, v) in headers.iter() {
request_builder = request_builder.header(k, v.clone());
}
// リクエストボディのタイプ (RequestBody) に基づいてリクエストボディを設定します。
match body {
// ボディがバイナリの場合は、コンテンツタイプを application/octet-stream に設定します。
RequestBody::Binary(binary_data) => {
request_builder = request_builder.header("Content-Type", "application/octet-stream");
request_builder = request_builder.body(binary_data.clone()); // シナリオに基づいて値を変更します。
}
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(_) => {
// 位置が formData であるパラメーターの 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!("リクエストの構築に失敗しました: {}", e))?;
let response = client
.execute(request)
.await
.map_err(|e| format!("リクエストの実行に失敗しました: {}", e))?;
Ok(response)
}
/// レスポンスを読み取ります。
pub async fn read_response(result: Response) -> Result<(StatusCode, String), String> {
let status = result.status();
let data = result.bytes().await.map_err(|e| format!("レスポンスボディの読み取りに失敗しました: {}", e))?;
let res = match str::from_utf8(&data) {
Ok(s) => s.to_string(),
Err(_) => return Err("ボディに UTF-8 以外の文字が含まれています".to_string()),
};
Ok((status, res))
}
/// 正規化クエリ文字列を構築します。
pub fn build_sored_encoded_query_string(query_params: &[(&str, &str)]) -> String {
// パラメーターをアルファベット順にソートし、BTreeMap を使用して重複パラメーターを処理します。
let sorted_query_params: BTreeMap<_, _> = query_params.iter().copied().collect();
// パラメーターを URI 形式でエンコードします。
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();
// アンパサンド (&) を使用して URI エンコードされたパラメーターを連結します。
encoded_params.join("&")
}
/**
* 署名のサンプル。 main() メソッドのパラメーターを調整する必要があります。
* ROA の API 操作と RPC スタイルの API 操作は、canonicalUri 値のロジックのみが異なります。
* リクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーター型 (type)、およびリクエストパラメーターの位置 (in) を取得し、署名リクエスト (SignatureRequest) に情報をカプセル化します。
*1. API メタデータのリクエストパラメーターに "in":"query" の位置情報が含まれている場合は、クエリ文字列 (queryParam) のパラメーターを指定します。
*2. API メタデータのリクエストパラメーターに "in": "body" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
*2. API メタデータのリクエストパラメーターに "in": "body" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
*/
#[tokio::main]
async fn main() {
// HTTP クライアントを作成します。
let client = Client::new();
// env::var() は、AccessKey ID と AccessKey シークレットが環境変数から取得されることを指定します。
let access_key_id = env::var("ALIBABA_CLOUD_ACCESS_KEY_ID").expect("アクセスキー ID を取得できません。");
let access_key_secret = env::var("ALIBABA_CLOUD_ACCESS_KEY_SECRET").expect("アクセスキー ID を取得できません。");
// 例 1: POST 経由で RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in":"query")
let host = "ecs.cn-hangzhou.aliyuncs.com"; // エンドポイント
let canonical_uri = "/"; // RPC API 操作のリソースパスは空です。したがって、スラッシュ (/) が CanonicalURI パラメーターの値として使用されます。
let action = "DescribeInstanceStatus"; // 実行する API 操作。
let version = "2014-05-26"; // API のバージョン番号。
// RegionId は API メタデータで文字列として定義されています。必須パラメーターであり、位置は "in":"query" です。
let query_params = &[("RegionId", "cn-hangzhou")];
// クエリ文字列を構築します。InstanceId は API メタデータの文字列です。オプションパラメーターであり、位置は "in":"query" です。
// let region_id = "cn-hangzhou";
// let instance_ids = vec![
// "i-bp11ht4h2kd1XXXXXXXX",
// "i-bp16maz3h3xgXXXXXXXX",
// "i-bp10r67hmsllXXXXXXXX",
// ];
// // // instance_ids をコンマ (,) で区切られた文字列に変換します。
// let instance_id_str = instance_ids.join(",");
// // クエリパラメーターを指定するときは、最初に RegionId を指定します。
// let mut query: Vec<(&str, &str)> = vec![("RegionId", region_id), ("InstanceId", &instance_id_str)];
// let query_params = &query[..];
// 例 2: POST 経由で RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in":"body")
// let host = "ocr-api.cn-hangzhou.aliyuncs.com";
// let canonical_uri = "/";
// let action = "RecognizeGeneral";
// let version = "2021-07-07";
// // // ファイルをアップロードします。
// let binary_data = std::fs::read("C:\\Users\\issuser\\Desktop\\img\\001.png").expect("ファイルの読み取りに失敗しました"); // パラメーターにバイナリファイルを指定します。
// 例 3: POST 経由で RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in": "formData")
// let host = "mt.aliyuncs.com";
// let canonical_uri = "/";
// let action = "TranslateGeneral";
// let version = "2018-10-12";
// // Context は API メタデータで文字列として定義されています。オプションパラメーターであり、位置は "in":"query" です。
// let query_params = &[("Context", "Morning")];
// // FormatType、SourceLanguage、および TargetLanguage パラメーターのメタデータの位置は "in":"formData" として定義されています。
// let mut body = HashMap::new(); // HashMap<String, FormValue> FormValue Vec<String>、HashSet<String>、または HashMap<String, String> を指定できます。さらに型を指定する場合は、FormValue パラメーターで列挙します。
// body.insert(String::from("FormatType"),FormValue::String(String::from("text")));
// body.insert(String::from("SourceLanguage"),FormValue::String(String::from("zh")));
// body.insert(String::from("TargetLanguage"),FormValue::String(String::from("en")));
// body.insert(String::from("SourceText"),FormValue::String(String::from("Hello")));
// body.insert(String::from("Scene"),FormValue::String(String::from("general")));
// ROA スタイルの CreateClusteroperation の POST リクエストを構築して、クラスターを作成します。
// API 操作の定数を指定します。
// let host = "cs.cn-beijing.aliyuncs.com";
// let canonical_uri = "/clusters";
// let action = "CreateCluster";
// let version = "2015-12-15";
// // リクエストボディパラメーターを指定します。
// let mut body = HashMap::new(); // HashMap<String, Value> サポートされている値: Value::String("test".to_string()) // String Value::Number(serde_json::Number::from(42)) // Number Value::Bool(true) // Boolean Value::Null // Null Value::Array(vec![Value::from(1), Value::from(2), Value::from(3)]) //Array json!({"nested_key": "nested_value"})
// body.insert(String::from("name"),json!("Test cluster"));
// body.insert(String::from("region_id"),json!("cn-beijing"));
// body.insert(String::from("cluster_type"),json!("ExternalKubernetes"));
// body.insert(String::from("vpcid"),json!("vpc-2zeou1uod4ylaXXXXXXXX"));
// body.insert(String::from("container_cidr"),json!("10.0.0.0/8"));
// body.insert(String::from("service_cidr"),json!("10.2.0.0/24"));
// body.insert(String::from("security_group_id"),json!("sg-2ze1a0rlgeo7XXXXXXXX"));
// body.insert(String::from("vswitch_ids"),json!(vec!["vsw-2zei30dhfldu8XXXXXXXX"]));
// ROA スタイルの DeleteCluster API 操作の GET リクエストを構築して、指定されたクラスターに関連付けられているリソースをクエリします。
// let host = "cs.cn-beijing.aliyuncs.com"; // エンドポイント
// // 値を連結してリソースパスを形成します。
// let uri = format!("/clusters/{}/resources", percent_code("c1311ba68f3af45f39ee3f4d2XXXXXXXX").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"
// ROA スタイルで API 操作を呼び出して、削除操作を実行します。 API 操作: DeleteCluster。API 操作を呼び出して、従量課金制クラスターを削除します。
// let host = "cs.cn-beijing.aliyuncs.com";
// let uri = format!("/clusters/{}", percent_code("c72b778e79d3647cdb95c8b86XXXXXXXX").as_ref());
// let canonical_uri = uri.as_str(); // リソースパスを &Str で識別される文字列スライスに変換します。
// let action = "DeleteCluster";
// let version = "2015-12-15";
// API 操作を呼び出します。
match call_api(
client.clone(),
Method::POST, // リクエストメソッド。サポートされているメソッドには、GET、DELETE、PUT、POST があります。
host, // エンドポイント
canonical_uri, // リソースパス。
// &[], // クエリパラメーターを指定する必要がない場合は、query_params を &[] に設定します。
query_params, // クエリパラメーターを指定する必要がある場合は、query_params を &[("K", "V")] に設定します。
action,
version,
RequestBody::None, // ボディタイプが指定されていない場合は、RequestBody::None を指定します。
// RequestBody::Json(body), // ボディタイプがマップの場合は、RequestBody::Json を指定してマップを渡します。
// RequestBody::Binary(binary_data), // ボディタイプがバイナリの場合は、RequestBody::Binary を指定してバイナリデータを渡します。
// RequestBody::FormData(body), // ボディタイプが formDate の場合は、RequestBody::FormData を指定して FormData 値を渡します。
&access_key_id,
&access_key_secret,
)
.await {
Ok(response) => println!("レスポンス: {}", response),
Err(error) => eprintln!("エラー: {}", 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 または formData タイプのパラメーターは、body で指定できます。
# パラメーターが body の場合、body の値は JSON 文字列です: "{'key1':'value1','key2':'value2'}". 署名計算に使用するヘッダーに content-type:application/json; charset=utf-8 を追加する必要があります。
# パラメーターがバイナリファイルの場合、body を変更する必要はありません。署名計算に使用するヘッダーに content-type:application/octet-stream を追加し、curl_command 変数に --data-binary を追加します。
# パラメーターが formData パラメーターの場合、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}"
# UTL エンコーディング関数。
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: 正規化リクエストを構築します。
# すべてのクエリパラメーターをフラット化します。
newQueryParam=()
# 各 raw クエリパラメーターをトラバースします。
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' | 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'"
# Authorization ヘッダーを追加します。
IFS=$'\n' # 改行 (\n) を新しい IFS として使用します。
for header in $headers; do
curl_command="$curl_command -H '$header'"
done
curl_command+=" -H 'Authorization:$authorization'"
# body がバイナリファイルの場合は、次のコード行をコメントアウトします。
curl_command+=" -d '$body'"
# body がバイナリファイルの場合は、次のコード行のコメントを解除します。
#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() は、AccessKey ID と AccessKey シークレットが環境変数から取得されることを指定します。
#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
// HMAC-SHA256 アルゴリズムを使用して計算を実行します。
void hmac256(const char *key, const char *message, char *output) {
unsigned char hmac[SHA256_DIGEST_LENGTH];
unsigned int result_len;
// HMAC 値を計算します。
HMAC(EVP_sha256(), key, strlen(key), (unsigned char *)message, strlen(message), hmac, &result_len);
// HMAC 値を 16 進数文字列に変換します。
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';
}
// 値を URL 形式でエンコードします。
char* percentEncode(const char* str) {
if (str == NULL) {
fprintf(stderr, "入力文字列を null にすることはできません\n");
return NULL;
}
size_t len = strlen(str);
// 最悪の場合、各文字は %XX 形式の 3 文字にエンコードする必要があります。そのため、割り当てられるスペースは元の文字の 3 倍のサイズになります。
char* encoded = (char*)malloc(len * 3 + 1);
if (encoded == NULL) {
fprintf(stderr, "メモリリソースの割り当てに失敗しました\n");
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 {
// それ以外の場合は、文字を URL 形式でエンコードします。
ptr += sprintf(ptr, "%%%02X", c);
}
}
*ptr = '\0'; // null で終了します。
// プラス記号 (+) を置き換えます。
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'; // null で終了します。
}
free(encoded); // 一時コードのスペースを解放します。
return finalEncoded;
}
// ランダムな nonce。
void generate_uuid(char *uuid, size_t size) {
if (size < 37) { // UUID 形式には 36 文字とターミネーターが必要です。
fprintf(stderr, "UUID のバッファーサイズが小さすぎます\n");
return;
}
// 乱数ジェネレーターを使用して 16 個のランダムバイトを生成します。
unsigned char random_bytes[16];
RAND_bytes(random_bytes, sizeof(random_bytes));
// 有効なバージョンは 4 です。これは、ランダムな UUID を生成するために使用されます。
random_bytes[6] &= 0x0f; // 上位 4 つの値を保持します。
random_bytes[6] |= 0x40; // バージョンを 4 に設定します。
random_bytes[8] &= 0x3f; // 上位 2 つの値を保持します。
random_bytes[8] |= 0x80; // バリアントを 10xx に設定します。
// 値を UUID 文字列にフォーマットします。
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, "ファイル %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, "ファイルバッファーのメモリ割り当てに失敗しました\n");
fclose(file);
return 0; // ファイルの読み取りに失敗しました。
}
fread(*buffer, 1, file_size, file);
fclose(file);
return file_size; // 読み取られたバイト数を返します。
}
// 認証を取得します。
void 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) {
// UUID を生成します。
generate_uuid(uuid, 37);
// x-acs-date 文字列を生成します。
time_t now = time(NULL);
struct tm *utc_time = gmtime(&now);
strftime(x_acs_date, 64, "%Y-%m-%dT%H:%M:%SZ", utc_time);
// タイムスタンプを出力します。
printf("生成された x-acs-date: %s\n", x_acs_date);
// SHA-256 アルゴリズムを使用してリクエストボディのハッシュ値を計算します (x-acs-content-sha256)。
sha256_hex(body ? body : "", hashed_payload);
// ハッシュ値を出力します。
printf("生成された x-acs-content-sha256: %s\n", hashed_payload);
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("正規化ヘッダー:===============>\n%s\n", canonical_headers);
// 署名計算に使用するヘッダー。
char signed_headers[] = "host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version";
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, query_params ? strdup(query_params) : "", // 次のヘッダーは署名計算に使用されます: percentCode、
canonical_headers, signed_headers, hashed_payload);
// 正規化リクエストを出力します。
printf("正規化リクエスト:\n%s\n", canonical_request);
// 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);
// 署名対象文字列を作成します。
char string_to_sign[BUFFER_SIZE];
snprintf(string_to_sign, sizeof(string_to_sign), "%s\n%s", ALGORITHM, hashed_canonical_request);
// 署名対象文字列を出力します。
printf("署名対象文字列:\n%s\n", string_to_sign);
// 署名を生成します。
char signature[SHA256_DIGEST_LENGTH * 2 + 1];
hmac256(ACCESS_KEY_SECRET, string_to_sign, signature);
// 署名を出力します。
printf("署名: %s\n", signature);
// 署名計算に使用するヘッダー (SignedHeaders) を含む最終的な Authorization ヘッダーを作成します。
snprintf(authorization_header, BUFFER_SIZE,
"%s Credential=%s,SignedHeaders=%s,Signature=%s",
ALGORITHM, ACCESS_KEY_ID, signed_headers, signature);
// Authorization ヘッダーを出力します。
printf("認証: %s\n", authorization_header);
}
// リクエストを送信します。
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];
// Authorization ヘッダーを取得します。
get_authorization(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, authorization_header, hashed_payload, x_acs_date, uuid);
// リクエスト URL を連結します。
char url[BUFFER_SIZE];
if (query_params && strlen(query_params) > 0) {
snprintf(url, sizeof(url), "https://%s%s?%s", host, canonical_uri, query_params);
} else {
snprintf(url, sizeof(url), "https://%s%s", host, canonical_uri);
}
// リクエスト URL を出力します。
printf("リクエスト URL: %s\n", url);
// cURL コマンドを初期化します。
CURL *curl = curl_easy_init();
if(!curl) {
fprintf(stderr, "curl_easy_init() に失敗しました\n");
return;
}
// 配列に追加するリクエストヘッダーを指定します。
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(headervalue, 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 オプションを設定します。
// cURL リクエストメソッドを指定します。
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, http_method); // HTTP メソッドを指定します。
// URL を指定します。
curl_easy_setopt(curl, CURLOPT_URL, url);
// デバッグ中に SSL 検証を無効にします。
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
// デバッグ情報を追加します。
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
// リクエストヘッダーを追加します。
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
// リクエストボディを追加します。
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);
}
}
// ヘッダーを出力します。
struct curl_slist *header_ptr = headers;
while (header_ptr) {
printf("ヘッダー: %s\n", header_ptr->data);
header_ptr = header_ptr->next;
}
// リクエストを送信し、レスポンスを確認します。
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() に失敗しました: %s\n", curl_easy_strerror(res));
return;
}
// データをクリアします。
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
/**
*
* 署名のサンプル。 main() メソッドのパラメーターを調整する必要があります。
* <p>
* リクエストメソッド (methods)、リクエストパラメーター名 (name)、リクエストパラメーター型 (type)、およびリクエストパラメーターの位置 (in) を取得し、署名リクエスト (SignatureRequest) に情報をカプセル化します。
*1. API メタデータのリクエストパラメーターに "in":"query" の位置情報が含まれている場合は、クエリ文字列 (queryParam) のパラメーターを指定します。
*2. API メタデータのリクエストパラメーターに "in": "body" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
*2. API メタデータのリクエストパラメーターに "in": "body" の位置情報が含まれている場合は、ボディのパラメーターを指定します。
*/
int main() {
// レスポンス形式を UTF-8 に設定します。
SetConsoleOutputCP(CP_UTF8);
srand((unsigned int)time(NULL));
/**
* 例: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in":"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 パラメーターを設定します (オプション)。値は配列です。
const char *instance_ids[] = {
"i-bp11ht4h2kd1ic5fXXXX",
"i-bp16maz3h3xg83raXXXX"
};
// InstanceId 配列を連結します。
char InstanceId[BUFFER_SIZE];
snprintf(InstanceId, sizeof(InstanceId),
"InstanceId.1=%s&InstanceId.2=%s",
percentEncode(instance_ids[0]),
percentEncode(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);
// パラメーターを出力します。
printf("クエリパラメーター: %s\n", query_params);
// body は空です。
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 操作を呼び出す (パラメーターの位置: "in":"body")
*/
// システムによって読み取られたファイルコンテンツを格納するためのポインターを宣言します。
// char *body = NULL;
// // システムによって読み取られたファイルの長さ。
// size_t body_length = read_file("C:\\Users\\issuser\\Desktop\\img\\001.png", &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";
//
// // API 操作を呼び出します。
// 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, "ファイル読み取りエラー\n");
// }
/**
* 例: RPC スタイルの API 操作を呼び出す (パラメーターの位置: "in": "formData")
*/
// 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";
// // 位置が formData であるパラメーターの場合は、エンコードされた文字を使用してパラメーター値をクエリします。
// // Context クエリパラメーターを設定します: Context="Morning"。
// char query_params[BUFFER_SIZE];
// snprintf(query_params, sizeof(query_params), "Context=%s", percentEncode("Morning"));
//
// // formdate パラメーターの値を指定します。
// const char *format_type = "text";
// const char *source_language = "zh";
// const char *target_language = "en";
// const char *source_text = "Hello";
// const char *scene = "general";
// // body が formdate タイプの場合は、フォームデータ文字列を作成してエンコードします。
// 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));
/**
* ROA スタイルの API 操作の POST リクエストを構築します。
*/
// 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 = "";
// // body は JSON タイプです。
// // JSON 形式のリクエストボディを作成します。
// 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-2zeou1uod4ylaf35tXXXX", "10.0.0.0/8",
// "10.2.0.0/24", "sg-2ze1a0rlgeo7dj37XXXX",
// "vsw-2zei30dhfldu8ytmtXXXX");
// // リクエストボディを出力します。
// printf("リクエストボディ: %s\n", 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));
/**
* ROA スタイルの API 操作の GET リクエストを構築します。
*/
// const char *http_method = "GET";
// // リクエストパラメーターを連結して URL を形成します (例: canonical_uri:/clusters/cluster_id/resources)。
// char canonical_uri[BUFFER_SIZE];
// snprintf(canonical_uri, sizeof(canonical_uri), "/clusters/%s/resources", percentEncode("cd1f5ba0dbfa144c69e48b75df47bXXXX"));
// // リソースパスを出力します。
// printf("canonical_uri: %s\n", canonical_uri);
//
// 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";
// // body を指定します。
// 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";
// // リクエストパラメーターを連結して URL を形成します (例: canonical_uri:/clusters/cluster_id)。
// char canonical_uri[BUFFER_SIZE];
// snprintf(canonical_uri, sizeof(canonical_uri), "/clusters/%s", percentEncode("cd1f5ba0dbfa144c69e48b75df47bXXXX"));
// // リソースパスを出力します。
// printf("canonical_uri: %s\n", canonical_uri);
// 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 = "";
// // body を指定します。
// 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;
}
よくある質問
署名の検証に失敗し、「指定された署名が計算と一致しません。」または「リクエスト署名がAliyun標準に準拠していません。」というエラーメッセージが返された場合はどうすればよいですか?
原因:ほとんどの場合、署名の検証は、署名計算プロセス中に必要な操作が実行されないために失敗します。一般的な原因:
URL で連結する必要があるパラメーターが本文で指定されています。
正規化されたクエリ文字列(CanonicalQueryString)のパラメーターがアルファベット順にソートされていません。
署名計算に使用されるヘッダー(SignedHeaders)がアルファベット順にソートされていません。
スペース文字が
%20
にエンコードされていません。
解決策:
オンプレミス マシンで署名計算結果が正しいかどうかを確認します。詳細については、このトピックの固定パラメーター値セクションをご参照ください。
エラーメッセージで CanonicalRequest パラメーターと StringToSign パラメーターの値を確認します。
まず、CanonicalRequest の値が、オンプレミス マシンで計算した結果と同じかどうかを比較します。同じでない場合は、このトピックの手順 1:正規化されたリクエストを作成するを参照して、サンプルコードと自分のコードの構成の違いを見つけます。最も一般的なエラーの 1 つは、パラメーターの配置ミスです。たとえば、URL で連結する必要があるパラメーターが本文で指定されている場合などです。これは、
CanonicalQueryString
パラメーターとx-acs-content-sha256
パラメーターでエラーが発生する原因となります。CanonicalRequest の値が、オンプレミス マシンで計算した結果と同じ場合は、StringToSign の値が、オンプレミス マシンで計算した結果と同じかどうかを比較します。StringToSign パラメーターの
HashedCanonicalRequest
の値は、x-acs-content-sha256
と同じ関数を使用して生成する必要があることに注意してください。問題が解決しない場合は、テクニカルサポートにリクエストしてください。
リクエスト パラメーターを渡すにはどうすればよいですか?
API メタデータは、各 API 操作のリクエスト パラメーターの渡し方を定義します。例:
"in":"query"
は、content-type 変数を使用せずに、パラメーターを URL で連結する必要があることを指定します。"in":"body"
は、パラメーターを本文で指定する必要があることを指定します。RequestHeader パラメーターに content-type 変数を追加する必要があります。content-type の値は、リクエストのコンテンツ タイプによって決まります。例:リクエスト コンテンツが JSON 文字列の場合は、Content-Type ヘッダーの値を
application/json
に設定します。リクエスト コンテンツがバイナリ ファイル ストリームの場合は、Content-Type ヘッダーの値を
application/octet-stream
に設定します。
"in":"formData"
は、パラメーターを本文で指定する必要があることを指定します。パラメーターを指定する場合は、key1=value1&key2=value2&key3=value
の形式でパラメーターを文字列に連結します。content-type 変数を RequestHeader パラメーターに追加し、content-type の値をapplication/x-www-form-urlencoded
に設定します。
API メタデータによると、style
の値は RPC または ROA です。
style の値は、CanonicalURI パラメーターの値にのみ影響します。style
を RPC および ROA 以外の値に設定する場合は、API メタデータを表示して、path
パラメーターが定義されているかどうかを確認します。定義されている場合は、CanonicalURI を path
の値に設定します。定義されていない場合は、CanonicalURI をスラッシュ (/
) に設定します。たとえば、ACK クラスタをクエリするために使用される API 操作のメタデータで path
の値が /api/v1/clusters
として定義されている場合は、CanonicalURI を /api/v1/cluster
に設定します。
配列またはオブジェクトタイプのパラメーターを渡すにはどうすればよいですか?
パラメーターが複雑な構造になっている場合は、データ構造をマップに変換する必要があります。たとえば、DescribeInstanceStatus API 操作の InstanceId パラメーターが配列タイプで、InstanceId に複数の値が指定されている場合は、パラメーターを次のマップに変換します。
{
"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"
}
API バージョンの x-acs-version 値を取得するにはどうすればよいですか?
Open Explorer にログインし、呼び出す API を選択します。例では、ECS API を使用します。
プロダクト ホームページで推奨される API バージョンを確認します。たとえば、ECS の推奨 API バージョンは 2014-05-26 です。
自己署名モードで、デバッグ中に GET メソッドを使用して API 操作を呼び出し、呼び出しが成功した場合、POST メソッドを使用して API 操作を呼び出すことはできますか?
ほとんどの場合、POST または GET メソッドを使用して RPC スタイルの API 操作を呼び出すことができます。
ただし、ROA スタイルの API 操作を呼び出すには、メソッドのみを使用できます。
API 操作の呼び出しに使用できるリクエスト メソッドの詳細については、API メタデータをご参照ください。
「この操作を実行する権限がありません。」というエラーメッセージが返された場合はどうすればよいですか?
原因:RAM ユーザーが使用している AccessKey ペアに、API 操作を呼び出す権限がありません。
解決策:サービスに対する管理権限または読み取り専用権限を RAM ユーザーに付与します。詳細については、RAM ユーザーに権限を付与するをご参照ください。
AccessKey ペアを取得するにはどうすればよいですか?
AccessKey ペアは、Alibaba Cloud がユーザーに提供する永続的なアクセス認証情報です。AccessKey ペアは、AccessKey ID と AccessKey シークレットで構成されます。API 操作を呼び出して Alibaba Cloud リソースにアクセスする場合、システムは、AccessKey ID と、AccessKey シークレットに基づいて生成された署名に基づいて、呼び出し元の ID とリクエストの有効性を認証します。AccessKey ペアの取得方法の詳細については、RAM ユーザーの AccessKey を作成するをご参照ください。
お問い合わせ
署名計算プロセス中に質問がある場合は、DingTalk グループ(ID: 78410016550
)に参加して技術サポートをリクエストしてください。