All Products
Search
Document Center

Alibaba Cloud SDK:Struktur permintaan dan signature V3

Last Updated:Jun 03, 2026

Jika Anda tidak dapat atau tidak ingin menggunakan SDK Alibaba Cloud, Anda dapat menandatangani permintaan HTTP secara manual untuk memanggil operasi API. Topik ini menjelaskan cara membuat signature V3 dan menandatangani permintaan.

Catatan penggunaan

  • Jika Anda masih menggunakan metode signature V2, segera beralih ke V3.

  • Signature V3 berlaku untuk layanan Alibaba Cloud yang SDK-nya tersedia di OpenAPI Explorer. Beberapa layanan menggunakan gateway mandiri dengan mekanisme otentikasi berbeda. Periksa dokumentasi signature khusus layanan sebelum mengirim permintaan HTTP.

Sintaksis permintaan HTTP

Permintaan API Alibaba Cloud terdiri dari komponen-komponen berikut.

Komponen

Wajib

Deskripsi

Contoh

Protokol

Ya

Protokol permintaan: HTTP atau HTTPS. Gunakan HTTPS demi keamanan. Nilai yang valid: https:// dan http://. Periksa referensi API untuk protokol yang didukung.

https://

Endpoint

Ya

Endpoint API layanan. Periksa referensi API untuk endpoint spesifik wilayah.

ecs.cn-shanghai.aliyuncs.com

resource_URI_parameters

Ya

URL resource, termasuk path dan parameter permintaan.

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

RequestHeader

Ya

Header permintaan umum yang berisi versi API, endpoint, dan informasi otentikasi. Lihat bagian RequestHeader di bawah.

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

Ya

Parameter body permintaan. Dapatkan detail parameter dari metadata OpenAPI.

HTTPMethod

Ya

Metode HTTP. Dapatkan metode dari metadata OpenAPI.

POST

RequestHeader

Sertakan header berikut dalam setiap permintaan API.

Header

Tipe

Wajib

Deskripsi

Contoh

host

String

Ya

Endpoint API layanan Alibaba Cloud. Lihat Sintaksis permintaan HTTP.

ecs.cn-shanghai.aliyuncs.com

x-acs-action

String

Ya

Operasi API yang akan dipanggil. Temukan operasi yang tersedia di Portal OpenAPI.

RunInstances

x-acs-content-sha256

String

Ya

Hash dari body permintaan yang di-encode Base16. Harus sesuai dengan nilai HashedRequestPayload.

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

x-acs-date

String

Ya

Timestamp permintaan dalam format ISO 8601 (yyyy-MM-ddTHH:mm:ssZ, UTC). Harus berada dalam rentang 15 menit dari waktu pengiriman permintaan.

2023-10-26T10:22:32Z

x-acs-signature-nonce

String

Ya

Bilangan acak unik untuk mencegah serangan replay. Gunakan nilai berbeda untuk setiap permintaan. Hanya berlaku untuk HTTP.

3156853299f313e23d1673dc12e1703d

x-acs-version

String

Ya

Nomor versi API. Lihat Cara mendapatkan nilai x-acs-version.

2014-05-26

Authorization

String

Ya jika permintaan bersifat non-anonim

Informasi otentikasi untuk validasi permintaan. Format: Authorization: SignatureAlgorithm Credential=AccessKeyId,SignedHeaders=SignedHeaders,Signature=Signature.

SignatureAlgorithm: metode enkripsi string signature. Tetapkan nilainya ke ACS3-HMAC-SHA256.

Credential: ID AccessKey Anda. Lihat di Konsol RAM. Buat pasangan AccessKey.

SignedHeaders: header permintaan yang digunakan untuk perhitungan signature. Gunakan semua header permintaan umum kecuali Authorization.

Signature: signature permintaan. Lihat bagian metode Signature di bawah.

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

x-acs-security-token

String

Ya jika Security Token Service (STS) digunakan untuk otentikasi

Token STS. Gunakan nilai SecurityToken dari respons AssumeRole.

Metode signature

Alibaba Cloud menandatangani setiap permintaan HTTP/HTTPS menggunakan ID AccessKey dan rahasia AccessKey Anda, lalu membandingkan signature tersebut dengan signature dalam permintaan untuk mengotentikasi pemanggil.

Penting

Semua permintaan dan respons di-encode dalam UTF-8.

Langkah 1: Buat permintaan yang dikanonisasi

Buat permintaan yang dikanonisasi menggunakan pseudocode berikut:

CanonicalRequest =
  HTTPRequestMethod + '\n' +    // Metode permintaan HTTP dalam huruf kapital.
  CanonicalURI + '\n' +         // Uniform Resource Identifier (URI) yang dikanonisasi.
  CanonicalQueryString + '\n' + // String kueri yang dikanonisasi.
  CanonicalHeaders + '\n' +     // Header permintaan yang dikanonisasi.
  SignedHeaders + '\n' +        // Header permintaan yang digunakan untuk perhitungan signature.
  HashedRequestPayload // Nilai hash dari body permintaan.

Metode permintaan (HTTPRequestMethod)

Metode permintaan HTTP dalam huruf kapital, seperti GET atau POST.

URI yang dikanonisasi (CanonicalURI)

Path resource yang di-encode URI antara endpoint dan string kueri, termasuk / di awal tetapi tidak termasuk ?. Encode setiap segmen path yang dipisahkan oleh / dalam UTF-8 sesuai RFC 3986. Aturan encoding:

  • Huruf, angka, tanda hubung (-), garis bawah (_), titik (.), dan tilde (~) tidak perlu di-encode.

    Karakter lain harus di-encode persen dalam format berikut: % + kode ASCII karakter dalam notasi heksadesimal. Misalnya, tanda kutip ganda (") di-encode sebagai %22. Tabel berikut menjelaskan beberapa karakter khusus sebelum dan sesudah encoding. Perhatikan karakter khusus ini.

    Sebelum encoding

    Sesudah encoding

    Karakter spasi ( )

    %20

    Asterisk (*)

    %2A

    %7E

    Judul (~)

Jika Anda menggunakan java.net.URLEncoder dalam pustaka standar Java, encode string berdasarkan pustaka tersebut. Dalam string yang di-encode, ganti tanda plus (+) dengan %20, asterisk (*) dengan %2A, dan %7E dengan tilde (~). Dengan cara ini, Anda dapat memperoleh string yang di-encode sesuai aturan encoding di atas.

Penting

Jika gaya API adalah RPC, gunakan garis miring maju (/) sebagai nilai parameter CanonicalURI.

Jika gaya API adalah ROA, encode nilai parameter path dalam metadata operasi API dan gunakan nilai yang di-encode sebagai nilai parameter CanonicalURI. Contoh: /api/v1/clusters.

String kueri yang dikanonisasi (CanonicalQueryString)

Dalam metadata API, jika parameter permintaan API mengandung informasi posisi "in":"query", Anda perlu menggabungkan parameter permintaan berdasarkan metode berikut:

  1. Urutkan semua parameter permintaan berdasarkan nama parameter secara alfabetis.

  2. Encode nama dan nilai parameter dalam UTF-8 berdasarkan RFC 3986. Aturan encoding sama dengan yang digunakan untuk membuat URI yang dikanonisasi.

  3. Gunakan tanda sama dengan (=) untuk menggabungkan nama dan nilai yang di-encode dari setiap parameter. Untuk parameter tanpa nilai, gunakan string kosong sebagai nilai parameter.

  4. Gunakan tanda ampersand (&) untuk menggabungkan parameter yang di-encode sesuai urutan yang diperoleh pada langkah sebelumnya.

Penting

Contoh:

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

HashedRequestPayload

Gunakan fungsi hash untuk mengonversi body permintaan dan encode nilai hash dalam Base16 untuk menghasilkan HashedRequestPayload. Kemudian, ubah nilai x-acs-content-sha256 dalam header permintaan menjadi nilai HashedRequestPayload. Pseudocode:

HashedRequestPayload = HexEncode(Hash(RequestBody))
  • Dalam metadata API, jika parameter permintaan mengandung informasi posisi "in": "body" atau "in": "formData", tentukan parameter dalam body permintaan.

    Catatan

    Jika permintaan tidak memiliki body, tetapkan nilai RequestBody ke string kosong.

    • Jika parameter permintaan mengandung informasi posisi "in": "formData", gabungkan menjadi string dalam format berikut: key1=value1&key2=value2&key3=value3, dan tambahkan header Content-Type serta tetapkan nilainya ke application/x-www-form-urlencoded. Misalnya, jika parameter permintaan bertipe array atau objek, konversi nilainya menjadi pasangan kunci-nilai yang diindeks.

    • Jika parameter permintaan mengandung informasi posisi "in": "body", tambahkan header Content-Type ke permintaan. Nilai header Content-Type menentukan jenis konten permintaan. Contoh:

      • Jika konten permintaan berupa string JSON, tetapkan nilai header Content-Type ke application/json.

      • Jika konten permintaan berupa aliran file biner, tetapkan nilai header Content-Type ke application/octet-stream.

  • Hash() menentukan fungsi hash. Hanya algoritma SHA-256 yang didukung.

  • HexEncode() meng-encode nilai hash dalam Base16. Fungsi ini mengembalikan nilai hash yang di-encode dalam format heksadesimal dengan huruf kecil.

Nilai contoh ketika body permintaan kosong:

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

Header yang dikanonisasi (CanonicalHeaders)

Gabungkan parameter dalam header permintaan berdasarkan metode berikut:

  1. Filter header permintaan yang dimulai dengan x-acs-, header Host, dan header Content-Type.

  2. Konversi nama header menjadi huruf kecil dan urutkan header secara alfabetis.

  3. Hapus spasi sebelum dan sesudah nilai setiap header.

  4. Gabungkan nama header dan nilai header menggunakan titik dua (:), dan tambahkan line feed (\n) di akhir pasangan nama-nilai untuk membentuk entri header yang dikanonisasi.

  5. Gabungkan beberapa entri header yang dikanonisasi menjadi satu string.

Catatan

Semua header permintaan kecuali header Authorization harus digunakan untuk perhitungan signature.

Pseudocode:

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

CanonicalHeaders = 
    CanonicalHeaderEntry0 + CanonicalHeaderEntry1 + ... + CanonicalHeaderEntryN

Contoh:

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

Header yang ditandatangani (SignedHeaders)

Nama header yang digunakan untuk perhitungan signature dalam huruf kecil, dipisahkan titik koma. Buat header yang ditandatangani sebagai berikut:

  • Konversi nama header yang dikanonisasi menjadi huruf kecil.

  • Urutkan header secara alfabetis dan gunakan titik koma (;) untuk memisahkan header.

    Pseudocode:

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

Contoh:

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

Langkah 2: Buat string-to-sign

Buat string-to-sign menggunakan pseudocode berikut:

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

    Hanya algoritma ACS3-HMAC-SHA256 yang didukung untuk perhitungan signature.

  • HashedCanonicalRequest

    Nilai hash dari permintaan yang dikanonisasi. Pseudocode berikut menunjukkan cara menghasilkan nilai hash:

    HashedCanonicalRequest = HexEncode(Hash(CanonicalRequest))
    • Hash() menentukan fungsi hash. Hanya algoritma SHA-256 yang didukung.

    • HexEncode() meng-encode nilai hash dalam Base16. Fungsi ini mengembalikan nilai hash yang di-encode dalam format heksadesimal dengan huruf kecil.

Contoh:

ACS3-HMAC-SHA256
7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259

Langkah 3: Hitung string signature

Hitung signature menggunakan pseudocode berikut:

Signature = HexEncode(SignatureMethod(Secret, StringToSign))
  • StringToSign: string-to-sign yang dibuat pada Langkah 2. Nilainya di-encode dalam UTF-8.

  • SignatureMethod: Tentukan HMAC-SHA256 sebagai algoritma signature.

  • Secret: rahasia AccessKey.

  • HexEncode: fungsi yang digunakan untuk meng-encode nilai dalam Base16.

Contoh:

06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0

Langkah 4: Tambahkan string signature ke permintaan

Setelah memperoleh string signature, tentukan header Authorization dalam permintaan dengan format berikut: Authorization:<SignatureAlgorithm> Credential=<AccessKeyId>,SignedHeaders=<SignedHeaders>,Signature=<Signature>.

Contoh:

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

Contoh signature

Untuk membantu Anda memahami metode signature di atas, bagian ini menyediakan contoh kode berikut untuk sepenuhnya mengimplementasikan metode signature dalam bahasa pemrograman utama. Kode contoh berikut hanya disediakan untuk membantu Anda memahami metode signature dan tidak berlaku secara global. Alibaba Cloud menyediakan SDK untuk berbagai bahasa pemrograman dan framework pengembangan. Kami menyarankan Anda menggunakan SDK Alibaba Cloud untuk memulai permintaan API dan secara otomatis menghasilkan signature untuk permintaan tersebut. Anda dapat mengembangkan aplikasi di Alibaba Cloud tanpa perlu menghitung signature secara manual.

Penting

Sebelum menandatangani permintaan, baca dan pahami metadata OpenAPI, metode permintaan API, parameter permintaan, tipe parameter permintaan, dan cara melewatkan parameter. Jika tidak, Anda mungkin gagal menandatangani permintaan.

Nilai parameter tetap

Dalam contoh ini, nilai contoh digunakan untuk menunjukkan bagaimana hasil di-output pada setiap langkah. Anda dapat menggunakan nilai contoh untuk mensimulasikan perhitungan, dan membandingkan hasil Anda dengan hasil contoh ini untuk menguji proses penandatanganan signature.

Parameter

Contoh

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

Parameter khusus operasi

ImageId

win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd

RegionId

cn-shanghai

Anda dapat melakukan langkah-langkah berikut untuk menandatangani permintaan:

  1. Buat permintaan yang dikanonisasi.

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

host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
  1. Buat string-to-sign.

ACS3-HMAC-SHA256
7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259
  1. Hitung string signature.

06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0
  1. Tambahkan string signature ke permintaan.

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

Catatan

Dalam contoh ini, lingkungan runtime JDK 1.8 digunakan. Sesuaikan parameter berdasarkan kebutuhan bisnis Anda.

Untuk menggunakan metode signature di Java, Anda harus menambahkan dependensi Maven berikut ke file 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.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

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

public class SignatureDemo {

    public static class SignatureRequest {
        // Metode HTTP
        private final String httpMethod;
        // Path permintaan.
        private final String canonicalUri;
        // endpoint
        private final String host;
        // Nama API
        private final String xAcsAction;
        // Versi API
        private final String xAcsVersion;
        // header
        private final Map<String, String> headers = new TreeMap<>();
        // Array byte dari parameter dalam body.
        private byte[] body;
        // String kueri. 
        private final Map<String, Object> queryParam = new TreeMap<>();

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

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

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

        public String getHttpMethod() {
            return httpMethod;
        }

        public String getCanonicalUri() {
            return canonicalUri;
        }

        public String getHost() {
            return host;
        }

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

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

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

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

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

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

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

        /**
         * Hitung dan hasilkan string signature
         */
        public static void getAuthorization(SignatureRequest signatureRequest,
                                            String accessKeyId, String accessKeySecret, String securityToken) {
            try {
                // Ratakan parameter kueri tipe List dan Map.
                Map<String, Object> processedQueryParams = new TreeMap<>();
                processObject(processedQueryParams, "", signatureRequest.getQueryParam());
                signatureRequest.getQueryParam().clear();
                signatureRequest.getQueryParam().putAll(processedQueryParams);

                // Langkah 1: Buat permintaan yang dikanonisasi.
                String canonicalQueryString = buildCanonicalQueryString(signatureRequest.getQueryParam());

                // Hitung nilai hash dari body permintaan.
                String hashedRequestPayload = calculatePayloadHash(signatureRequest.getBody());
                signatureRequest.setHeaders("x-acs-content-sha256", hashedRequestPayload);

                // Tambahkan token keamanan jika ada.
                if (securityToken != null && !securityToken.isEmpty()) {
                    signatureRequest.setHeaders("x-acs-security-token", securityToken);
                }

                // Bangun header yang dikanonisasi dan header yang ditandatangani.
                CanonicalHeadersResult canonicalHeadersResult = buildCanonicalHeaders(signatureRequest.getHeaders());

                // Bangun permintaan yang dikanonisasi.
                String canonicalRequest = String.join("\n",
                        signatureRequest.getHttpMethod(),
                        signatureRequest.getCanonicalUri(),
                        canonicalQueryString,
                        canonicalHeadersResult.canonicalHeaders,
                        canonicalHeadersResult.signedHeaders,
                        hashedRequestPayload);

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

                // Langkah 2: Buat string-to-sign.
                String hashedCanonicalRequest = sha256Hex(canonicalRequest.getBytes(StandardCharsets.UTF_8));
                String stringToSign = ALGORITHM + "\n" + hashedCanonicalRequest;
                System.out.println("stringToSign=========>\n" + stringToSign);

                // Langkah 3: Hitung string signature.
                String signature = DatatypeConverter.printHexBinary(
                                hmac256(accessKeySecret.getBytes(StandardCharsets.UTF_8), stringToSign))
                        .toLowerCase();
                System.out.println("signature=========>" + signature);

                // Langkah 4: Tentukan header Authorization.
                String authorization = String.format("%s Credential=%s,SignedHeaders=%s,Signature=%s",
                        ALGORITHM, accessKeyId, canonicalHeadersResult.signedHeaders, signature);

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

        /**
         * Tangani parameter tipe formData. 
         */
        private static String formDataToString(Map<String, Object> formData) {
            Map<String, Object> tileMap = new HashMap<>();
            processObject(tileMap, "", formData);
            StringBuilder result = new StringBuilder();
            boolean first = true;
            String symbol = "&";
            for (Map.Entry<String, Object> entry : tileMap.entrySet()) {
                String value = String.valueOf(entry.getValue());
                if (value != null && !value.isEmpty()) {
                    if (first) {
                        first = false;
                    } else {
                        result.append(symbol);
                    }
                    result.append(percentCode(entry.getKey()));
                    result.append("=");
                    result.append(percentCode(value));
                }
            }

            return result.toString();
        }

        /**
         * Buat string kueri yang dikanonisasi.
         */
        private static String buildCanonicalQueryString(Map<String, Object> queryParams) {
            return queryParams.entrySet().stream()
                    .map(entry -> percentCode(entry.getKey()) + "=" +
                            percentCode(String.valueOf(entry.getValue())))
                    .collect(Collectors.joining("&"));
        }

        /**
         * Hitung nilai hash dari body permintaan.
         */
        private static String calculatePayloadHash(byte[] body) throws Exception {
            if (body != null) {
                return sha256Hex(body);
            } else {
                return sha256Hex("".getBytes(StandardCharsets.UTF_8));
            }
        }

        /**
         * Bangun header yang dikanonisasi.
         */
        private static CanonicalHeadersResult buildCanonicalHeaders(Map<String, String> headers) {
            List<Map.Entry<String, String>> signedHeaders = headers.entrySet().stream()
                    .filter(entry -> {
                        String key = entry.getKey().toLowerCase();
                        return key.startsWith("x-acs-") || "host".equals(key) || "content-type".equals(key);
                    })
                    .sorted(Map.Entry.comparingByKey())
                    .collect(Collectors.toList());

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

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

            if (signedHeadersString.length() > 0) {
                signedHeadersString.setLength(signedHeadersString.length() - 1); // Hapus titik koma di akhir.
            }

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

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

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

        /**
         * Proses parameter objek kompleks.
         */
        private static void processObject(Map<String, Object> map, String key, Object value) {
            if (value == null) {
                return;
            }

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

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

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

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

        /**
         * Hitung nilai hash menggunakan algoritma SHA-256.
         */
        private static String sha256Hex(byte[] input) throws Exception {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] digest = md.digest(input);
            return DatatypeConverter.printHexBinary(digest).toLowerCase();
        }

        /**
         * Lakukan encoding URL.
         */
        public static String percentCode(String str) {
            if (str == null) {
                return "";
            }
            try {
                return URLEncoder.encode(str, "UTF-8")
                        .replace("+", "%20")
                        .replace("*", "%2A")
                        .replace("%7E", "~");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("Encoding UTF-8 tidak didukung", e);
            }
        }
    }

    /**
     * Contoh signature. Anda perlu menyesuaikan parameter dalam metode main(). 
     * Operasi API dalam gaya ROA dan operasi API dalam gaya RPC hanya berbeda dalam logika nilai canonicalUri. 
     * <p>
     * Dapatkan metode permintaan (methods), nama parameter permintaan (name), tipe parameter permintaan (type), dan posisi parameter permintaan (in) serta kapsulkan informasi dalam permintaan signature (SignatureRequest). 
     *1. Jika parameter permintaan dalam metadata API mengandung informasi posisi "in":"query", tentukan parameter dalam string kueri (queryParam). Catatan: Anda juga dapat menentukan tipe parameter ini dalam body dan mengatur content-type ke application/x-www-form-urlencoded. Untuk informasi lebih lanjut, lihat Contoh 3. 
     *2. Jika parameter permintaan dalam metadata API mengandung informasi posisi "in": "body", tentukan parameter dalam body dan atur MIME ke application/octet-stream atau application/json. Catatan: Untuk API RPC, application/json tidak disarankan; gunakan metode dalam Contoh 3 sebagai gantinya. 
     3. Jika parameter permintaan dalam metadata API mengandung informasi posisi "in": "formData", tentukan parameter dalam body dan atur MIME ke application/x-www-form-urlencoded. 
     */
    public static void main(String[] args) throws IOException {
        // Dapatkan ID AccessKey dari variabel lingkungan.
        String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
        String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
        String securityToken = System.getenv("ALIBABA_CLOUD_SECURITY_TOKEN");

        if (accessKeyId == null || accessKeySecret == null) {
            System.err.println("Harap atur variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_ID dan ALIBABA_CLOUD_ACCESS_KEY_SECRET.");
            return;
        }

        // Contoh 1: Panggil operasi DescribeInstanceStatus dari Elastic Compute Service (ECS) dalam gaya RPC (Posisi parameter: "in":"query"). 
        SignatureRequest signatureRequest = new SignatureRequest(
                "POST",
                "/",
                "ecs.cn-hangzhou.aliyuncs.com",
                "DescribeInstanceStatus",
                "2014-05-26"
        );
        signatureRequest.setQueryParam("RegionId", "cn-hangzhou");
        signatureRequest.setQueryParam("InstanceId", Arrays.asList("i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"));

        /*// Contoh 2: Panggil operasi RecognizeGeneral dalam gaya RPC dari OCR (Posisi parameter: "in":"body"). 
        SignatureRequest signatureRequest = new SignatureRequest(
                "POST",
                "/",
                "ocr-api.cn-hangzhou.aliyuncs.com",
                "RecognizeGeneral",
                "2021-07-07");
        signatureRequest.setBody(Files.readAllBytes(Paths.get("D:\\test.jpeg")));
        signatureRequest.setHeaders("content-type", "application/octet-stream");*/

        /*// Contoh 3: Panggil operasi TranslateGeneral dari Machine Translation dalam gaya RPC (Posisi parameter: "in": "formData" atau "in":"body"). 
        String httpMethod = "POST";
        String canonicalUri = "/";
        String host = "mt.aliyuncs.com";
        String xAcsAction = "TranslateGeneral";
        String xAcsVersion = "2018-10-12";
        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
        Map<String, Object> body = new HashMap<>();
        body.put("FormatType", "text");
        body.put("SourceLanguage", "zh");
        body.put("TargetLanguage", "en");
        body.put("SourceText", "Hello");
        body.put("Scene", "general");
        String formDataToString = SignatureService.formDataToString(body);
        signatureRequest.setBody(formDataToString.getBytes(StandardCharsets.UTF_8));
        signatureRequest.setHeaders("content-type", "application/x-www-form-urlencoded");*/

        /*// Buat permintaan POST untuk operasi CreateCluster dari Container Service for Kubernetes (ACK) dalam gaya ROA. 
        SignatureRequest signatureRequest = new SignatureRequest(
                "POST",
                "/clusters",
                "cs.cn-chengdu.aliyuncs.com",
                "CreateCluster",
                "2015-12-15");
        TreeMap<String, Object> body = new TreeMap<>();
        body.put("name", "Test");
        body.put("cluster_type", "ManagedKubernetes");
        body.put("kubernetes_version", "1.34.1-aliyun.1");
        body.put("region_id", "cn-chengdu");
        body.put("snat_entry", true);
        body.put("deletion_protection", true);
        body.put("proxy_mode", "ipvs");
        body.put("profile", "Default");
        body.put("timezone", "Asia/Shanghai");
        body.put("cluster_spec", "ack.pro.small");
        body.put("enable_rrsa", false);
        body.put("service_cidr", "192.168.0.0/16");
        body.put("zone_ids", Arrays.asList("cn-chengdu-b","cn-chengdu-b"));
        Gson gson = (new GsonBuilder()).disableHtmlEscaping().create();
        signatureRequest.setBody(gson.toJson(body).getBytes(StandardCharsets.UTF_8));
        signatureRequest.setHeaders("content-type", "application/json");*/

        /*// Buat permintaan GET untuk operasi DescribeClusterResources dari ACK dalam gaya ROA. 
        SignatureRequest signatureRequest = new SignatureRequest(
                "GET",
                "/clusters/" + SignatureService.percentCode("c299f90b63b************") + "/resources",
                "cs.cn-chengdu.aliyuncs.com",
                "DescribeClusterResources",
                "2015-12-15");
        signatureRequest.setQueryParam("with_addon_resources", true);*/

        /*// Buat permintaan DELETE untuk operasi DeleteCluster dari ACK dalam gaya ROA. 
        SignatureRequest signatureRequest = new SignatureRequest(
                "DELETE",
                "/clusters/" + SignatureService.percentCode("c299f90b63b************"),
                "cs.cn-chengdu.aliyuncs.com",
                "DeleteCluster",
                "2015-12-15");*/

        // Hasilkan signature.
        SignatureService.getAuthorization(signatureRequest, accessKeyId, accessKeySecret, securityToken);

        // Uji apakah API dapat dipanggil berhasil
        callApi(signatureRequest);
    }

    /**
     * Hanya untuk pengujian
     */
    private static void callApi(SignatureRequest signatureRequest) {
        try {
            String url = "https://" + signatureRequest.getHost() + signatureRequest.getCanonicalUri();
            URIBuilder uriBuilder = new URIBuilder(url);

            // Tentukan parameter kueri.
            for (Map.Entry<String, Object> entry : signatureRequest.getQueryParam().entrySet()) {
                uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue()));
            }
            HttpUriRequest httpRequest;
            switch (signatureRequest.getHttpMethod()) {
                case "GET":
                    httpRequest = new HttpGet(uriBuilder.build());
                    break;
                case "POST":
                    HttpPost httpPost = new HttpPost(uriBuilder.build());
                    if (signatureRequest.getBody() != null) {
                        httpPost.setEntity(new ByteArrayEntity(signatureRequest.getBody(), ContentType.create(signatureRequest.getHeaders().get("content-type"))));
                    }
                    httpRequest = httpPost;
                    break;
                case "DELETE":
                    httpRequest = new HttpDelete(uriBuilder.build());
                    break;
                default:
                    System.out.println("Metode HTTP tidak didukung: " + signatureRequest.getHttpMethod());
                    throw new IllegalArgumentException("Metode HTTP tidak didukung");
            }

            // Tambahkan header permintaan.
            for (Map.Entry<String, String> entry : signatureRequest.getHeaders().entrySet()) {
                httpRequest.addHeader(entry.getKey(), entry.getValue());
            }

            // Kirim permintaan.
            try (CloseableHttpClient httpClient = HttpClients.createDefault();
                 CloseableHttpResponse response = httpClient.execute(httpRequest)) {
                String result = EntityUtils.toString(response.getEntity(), "UTF-8");
                System.out.println("Respons API: " + result);
            }
        } catch (IOException | URISyntaxException e) {
            throw new RuntimeException("Gagal memanggil API", e);
        }
    }
}

Python

Catatan

Dalam contoh ini, lingkungan runtime Python 3.12.3 digunakan. Sesuaikan parameter berdasarkan kebutuhan bisnis Anda.

Anda harus menginstal library pytz dan requests secara manual. Jalankan perintah berikut di terminal sesuai versi Python Anda:

Python3

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

import pytz
import requests

class SignatureRequest:
    def __init__(
            self,
            http_method: str,
            canonical_uri: str,
            host: str,
            x_acs_action: str,
            x_acs_version: str
    ):
        self.http_method = http_method
        self.canonical_uri = canonical_uri
        self.host = host
        self.x_acs_action = x_acs_action
        self.x_acs_version = x_acs_version
        self.headers = self._init_headers()
        self.query_param = OrderedDict()  # type: Dict[str, Any]
        self.body = None  # type: Optional[bytes]

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

    def sorted_query_params(self) -> None:
        """Mengurutkan parameter kueri secara alfabetis berdasarkan nama."""
        self.query_param = dict(sorted(self.query_param.items()))

    def sorted_headers(self) -> None:
        """Mengurutkan header permintaan secara alfabetis berdasarkan nama."""
        self.headers = dict(sorted(self.headers.items()))

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

        # Langkah 1: Buat permintaan yang dikanonisasi.
        canonical_query_string = "&".join(
            f"{percent_code(quote_plus(k))}={percent_code(quote_plus(str(v)))}"
            for k, v in request.query_param.items()
        )
        hashed_request_payload = sha256_hex(request.body or b'')
        request.headers['x-acs-content-sha256'] = hashed_request_payload

        if SECURITY_TOKEN:
            signature_request.headers["x-acs-security-token"] = SECURITY_TOKEN
        request.sorted_headers()

        filtered_headers = OrderedDict()
        for k, v in request.headers.items():
            if k.lower().startswith("x-acs-") or k.lower() in ["host", "content-type"]:
                filtered_headers[k.lower()] = v

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

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

        # Langkah 2: Buat string-to-sign.
        hashed_canonical_request = sha256_hex(canonical_request.encode("utf-8"))
        string_to_sign = f"{ALGORITHM}\n{hashed_canonical_request}"
        print(string_to_sign)

        # Langkah 3: Hitung signature.
        signature = hmac256(ACCESS_KEY_SECRET.encode("utf-8"), string_to_sign).hex().lower()

        # Langkah 4: Tambahkan signature ke header Authorization.
        authorization = f'{ALGORITHM} Credential={ACCESS_KEY_ID},SignedHeaders={signed_headers},Signature={signature}'
        request.headers["Authorization"] = authorization
    except Exception as e:
        print("Gagal mendapatkan authorization")
        print(e)

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

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

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

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

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

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

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

    try:
        response = requests.request(
            method=request.http_method, url=url, headers=headers, data=data
        )
        response.raise_for_status()
        print(response.text)
    except requests.RequestException as e:
        print("Gagal mengirim permintaan")
        print(e)

def percent_code(encoded_str: str) -> str:
    return encoded_str.replace("+", "%20").replace("*", "%2A").replace("%7E", "~")

# Dapatkan ID access key dan rahasia access key dari variabel lingkungan.
ACCESS_KEY_ID = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_ID")
ACCESS_KEY_SECRET = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
SECURITY_TOKEN = os.environ.get("ALIBABA_CLOUD_SECURITY_TOKEN")

ALGORITHM = "ACS3-HMAC-SHA256"

"""
Skrip ini memberikan contoh perhitungan signature API. Untuk menjalankan pengujian, hapus komentar pada contoh dalam fungsi main dan modifikasi nilainya.
Perbedaan utama antara API ROA dan RPC adalah cara membangun `canonical_uri`.

Dapatkan metode permintaan, nama parameter, tipe, dan lokasi dari metadata API, lalu gunakan untuk menginisialisasi objek `SignatureRequest`.
1. Jika lokasi parameter adalah "in": "query" dalam metadata API, tambahkan ke kamus `query_param`. Anda tidak perlu mengatur content-type. Catatan: Untuk API RPC, Anda juga dapat melewatkan parameter ini dalam body permintaan dengan content-type `application/x-www-form-urlencoded`. Lihat Contoh 3.
2. Jika lokasi parameter adalah "in": "body", lewatkan dalam body permintaan. Atur header `content-type` berdasarkan format muatan.
3. Jika lokasi parameter adalah "in": "formData", lewatkan dalam body permintaan dan atur header `content-type` ke `application/x-www-form-urlencoded`.
"""
if __name__ == "__main__":
    # Contoh 1: Permintaan API RPC dengan parameter dalam kueri
    http_method = "POST"  # Metode permintaan, yang bisa Anda dapatkan dari metadata API. Kami sarankan menggunakan POST.
    canonical_uri = "/"  # Untuk API RPC, path resource kosong. Gunakan garis miring (/) untuk `canonical_uri`.
    host = "ecs.cn-hangzhou.aliyuncs.com"  # Endpoint layanan.
    x_acs_action = "DescribeInstanceStatus"  # Operasi API.
    x_acs_version = "2014-05-26"  # Versi API.
    signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
    # Parameter permintaan untuk DescribeInstanceStatus adalah sebagai berikut:
    # Dalam metadata API, `RegionId` adalah string wajib dalam kueri.
    signature_request.query_param['RegionId'] = 'cn-hangzhou'
    # Dalam metadata API, `InstanceId` adalah array opsional dalam kueri.
    signature_request.query_param['InstanceId'] = ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX",
                                                   "i-bp1incuofvzxXXXXXXXX"]

    # # Contoh 2: Permintaan API RPC dengan parameter dalam body (skenario unggah file)
    # 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)
    # # Parameter ini dilewatkan dalam body permintaan, sebagaimana ditentukan oleh "in": "body" dalam metadata API.
    # file_path = "D:\\test.png"
    # with open(file_path, 'rb') as file:
    #     # Baca konten gambar sebagai array byte.
    #     signature_request.body = file.read()
    #     signature_request.headers["content-type"] = "application/octet-stream"

    # # Contoh 3: Permintaan API RPC dengan parameter dalam "formData" atau body (skenario bukan unggah file)
    # 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)
    # # Parameter permintaan untuk TranslateGeneral adalah sebagai berikut:
    # # Dalam metadata API, `Context` adalah string opsional dalam kueri.
    # signature_request.query_param['Context'] = 'Morning'
    # # Parameter seperti `FormatType`, `SourceLanguage`, dan `TargetLanguage` berada dalam `formData` dalam metadata API.
    # 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"

    # # Contoh 4: Permintaan POST API ROA
    # 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)
    # # Parameter ini dilewatkan dalam body permintaan, sebagaimana ditentukan oleh "in": "body" dalam metadata API.
    # 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"

    # # Contoh 5: Permintaan GET API ROA
    # http_method = "GET"
    # # Jika `canonical_uri` berisi parameter path, encode dengan menggunakan fungsi `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

    # # Contoh 6: Permintaan DELETE API ROA
    # http_method = "DELETE"
    # # Jika `canonical_uri` berisi parameter path, encode dengan menggunakan fungsi `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)

    get_authorization(signature_request)
    call_api(signature_request)

Go

Catatan

Dalam contoh ini, lingkungan runtime go1.22.2 digunakan. Sesuaikan parameter berdasarkan kebutuhan bisnis Anda.

Anda harus menjalankan perintah berikut di terminal:

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

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

	"golang.org/x/exp/maps"

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

	"github.com/google/uuid"
)

type Request struct {
	httpMethod   string
	canonicalUri string
	host         string
	xAcsAction   string
	xAcsVersion  string
	headers      map[string]string
	body         []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() menentukan bahwa ID AccessKey dan rahasia AccessKey diperoleh dari variabel lingkungan. 
var (
	AccessKeyId     = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
	AccessKeySecret = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
	SecurityToken   = os.Getenv("ALIBABA_CLOUD_SECURITY_TOKEN")
	ALGORITHM       = "ACS3-HMAC-SHA256"
)

// Contoh signature. Anda perlu menyesuaikan parameter dalam metode main(). 
// Operasi API dalam gaya ROA dan operasi API dalam gaya RPC hanya berbeda dalam logika nilai canonicalUri. 
// Dapatkan metode permintaan (methods), nama parameter permintaan (name), tipe parameter permintaan (type), dan posisi parameter permintaan (in) serta kapsulkan informasi dalam permintaan signature (SignatureRequest). 
// 1. Jika parameter permintaan dalam metadata API mengandung informasi posisi "in":"query", tentukan parameter dalam string kueri (queryParam). Catatan: Anda juga dapat menentukan tipe parameter ini dalam body dan mengatur content-type ke application/x-www-form-urlencoded. Untuk informasi lebih lanjut, lihat Contoh 3. 
*2. Jika parameter permintaan dalam metadata API mengandung informasi posisi "in": "body", tentukan parameter dalam body dan atur MIME ke application/octet-stream atau application/json. Catatan: Untuk API RPC, application/json tidak disarankan; gunakan metode dalam Contoh 3 sebagai gantinya. 
3. Jika parameter permintaan dalam metadata API mengandung informasi posisi "in": "formData", tentukan parameter dalam body dan atur MIME ke application/x-www-form-urlencoded. 
func main() {
	// Contoh 1: Panggil operasi API dalam gaya RPC (Posisi parameter: "in":"query")
	httpMethod := "POST"                   // Metode permintaan HTTP. Dalam kebanyakan kasus, Anda dapat menggunakan metode POST atau GET untuk memanggil operasi API dalam gaya RPC. Dalam contoh ini, POST digunakan.
	canonicalUri := "/"                    // Path resource operasi API RPC kosong. Oleh karena itu, garis miring (/) digunakan sebagai nilai parameter CanonicalURI.
	host := "ecs.cn-hangzhou.aliyuncs.com" // Endpoint layanan cloud.
	xAcsAction := "DescribeInstanceStatus" // Operasi API yang ingin Anda lakukan.
	xAcsVersion := "2014-05-26"            // Nomor versi API.
	req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
	// Parameter permintaan untuk memanggil operasi DescribeInstanceStatus:
	// RegionId didefinisikan sebagai string dalam metadata API. Ini adalah parameter wajib dan posisinya "in":"query".
	req.queryParam["RegionId"] = "cn-hangzhou"
	// InstanceId didefinisikan sebagai string dalam metadata API. Ini adalah parameter opsional dan posisinya "in":"query".
	instanceIds := []interface{}{"i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"}
	req.queryParam["InstanceId"] = instanceIds

	// // Contoh 2: Panggil operasi API dalam gaya RPC (Posisi parameter: "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)
	// Baca konten file gambar.
	// filePath := "D:\\test.png"
	// bytes, err := os.ReadFile(filePath)
	// if err != nil {
	//     fmt.Println("Error reading file:", err)
	//     return
	// }
	// req.body = bytes
	// req.headers["content-type"] = "application/octet-stream"

	// // Contoh 3: Panggil operasi API dalam gaya RPC (Posisi parameter: "in": "formData" atau "in":"body")
	// httpMethod := "POST"
	// canonicalUri := "/"
	// host := "mt.aliyuncs.com"
	// xAcsAction := "TranslateGeneral"
	// xAcsVersion := "2018-10-12"
	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
	// // Parameter permintaan untuk memanggil operasi TranslateGeneral:
	// // Context didefinisikan sebagai string dalam metadata API. Ini adalah parameter opsional dan posisinya "in":"query".
	// req.queryParam["Context"] = "Morning"
	// // Posisi parameter FormatType, SourceLanguage, dan TargetLanguage dalam metadata didefinisikan sebagai "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"

	// // Buat permintaan POST untuk operasi API dalam gaya ROA.
	// httpMethod := "POST"
	// canonicalUri := "/clusters"
	// host := "cs.cn-beijing.aliyuncs.com"
	// xAcsAction := "CreateCluster"
	// xAcsVersion := "2015-12-15"
	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
	// // Kapsulkan parameter permintaan. Jika parameter permintaan dalam metadata mengandung informasi posisi "in": "body", tentukan parameter dalam body.
	// body := make(map[string]interface{})
	// body["name"] = "testDemo"
	// body["region_id"] = "cn-beijing"
	// body["cluster_type"] = "ExternalKubernetes"
	// body["vpcid"] = "vpc-2zeou1uod4ylaXXXXXXXX"
	// body["container_cidr"] = "10.0.0.0/8"
	// body["service_cidr"] = "172.16.1.0/20"
	// body["security_group_id"] = "sg-2ze1a0rlgeo7XXXXXXXX"
	// vswitch_ids := []interface{}{"vsw-2zei30dhfldu8XXXXXXXX"}
	// body["vswitch_ids"] = vswitch_ids
	// jsonBytes, err := json.Marshal(body)
	// if err != nil {
	//     fmt.Println("Error marshaling to JSON:", err)
	//     return
	// }
	// req.body = []byte(jsonBytes)
	// req.headers["content-type"] = "application/json; charset=utf-8"

	// // Buat permintaan GET untuk operasi API ROA.
	// httpMethod := "GET"
	// // Jika nilai parameter canonicalUri berisi nilai parameter path, encode nilai parameter path dengan menentukan percentCode({Nilai parameter path}).
	// 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"

	// // Buat permintaan DELETE untuk operasi API ROA.
	// httpMethod := "DELETE"
	// // Jika nilai parameter canonicalUri berisi nilai parameter path, encode nilai parameter path dengan menentukan percentCode({Nilai parameter path}).
	// canonicalUri := "/clusters/" + percentCode("c558c166928f9446dae400d106e124f66")
	// host := "cs.cn-beijing.aliyuncs.com"
	// xAcsAction := "DeleteCluster"
	// xAcsVersion := "2015-12-15"
	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)

	// Tandatangani permintaan.
	getAuthorization(req)
	// Panggil operasi 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) {
	// Ratakan parameter kueri tipe List dan Map.
	newQueryParams := make(map[string]interface{})
	processObject(newQueryParams, "", req.queryParam)
	req.queryParam = newQueryParams
	// Langkah 1: Buat permintaan yang dikanonisasi.
	canonicalQueryString := ""
	keys := maps.Keys(req.queryParam)
	sort.Strings(keys)
	for _, k := range keys {
		v := req.queryParam[k]
		canonicalQueryString += percentCode(url.QueryEscape(k)) + "=" + percentCode(url.QueryEscape(fmt.Sprintf("%v", v))) + "&"
	}
	canonicalQueryString = strings.TrimSuffix(canonicalQueryString, "&")
	fmt.Printf("canonicalQueryString========>%s\n", canonicalQueryString)

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

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

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

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

	// Langkah 2: Buat string-to-sign.
	hashedCanonicalRequest := sha256Hex([]byte(canonicalRequest))
	stringToSign := ALGORITHM + "\n" + hashedCanonicalRequest
	fmt.Printf("stringToSign========>\n%s\n", stringToSign)

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

	// Langkah 4: Tentukan header Authorization.
	authorization := ALGORITHM + " Credential=" + AccessKeyId + ",SignedHeaders=" + signedHeaders + ",Signature=" + signature
	req.headers["Authorization"] = authorization
}

func hmac256(key []byte, toSignString string) ([]byte, error) {
	// Dapatkan objek tempat algoritma HMAC-SHA256 diimplementasikan.
	h := hmac.New(sha256.New, key)
	// Buat string-to-sign.
	_, err := h.Write([]byte(toSignString))
	if err != nil {
		return nil, err
	}
	// Hitung nilai hash dari string signature dan kembalikan nilai hash tersebut.
	return h.Sum(nil), nil
}

func sha256Hex(byteArray []byte) string {
	// Dapatkan objek tempat algoritma SHA-256 diimplementasikan.
	hash := sha256.New()
	// Tulis string signature ke fungsi hash.
	_, _ = hash.Write(byteArray)
	// Hitung nilai hash dari string signature menggunakan algoritma SHA-256 dan kembalikan nilai hash tersebut sebagai string dalam format heksadesimal dengan huruf kecil.
	hexString := hex.EncodeToString(hash.Sum(nil))

	return hexString
}

func percentCode(str string) string {
	// Ganti karakter tertentu.
	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: objek tempat rekursi dilakukan. Objek kompleks seperti map dan list secara rekursif diuraikan menjadi pasangan kunci-nilai.
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

Catatan

Dalam contoh ini, lingkungan runtime Node.js v20.13.1 digunakan. Sesuaikan parameter berdasarkan kebutuhan bisnis Anda.

Dalam contoh ini, Node.js digunakan.

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

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

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

const ALGORITHM = 'ACS3-HMAC-SHA256';
const accessKeyId = process.env.ALIBABA_CLOUD_ACCESS_KEY_ID;
const accessKeySecret = process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET;
const securityToken = process.env.ALIBABA_CLOUD_SECURITY_TOKEN;
const encoder = new TextEncoder()

if (!accessKeyId || !accessKeySecret) {
    console.error('Variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_ID dan ALIBABA_CLOUD_ACCESS_KEY_SECRET harus diatur.');
    process.exit(1);
}

function getAuthorization(signRequest) {
    try {
        newQueryParam = {};
        processObject(newQueryParam, "", signRequest.queryParam);
        signRequest.queryParam = newQueryParam;
        // Langkah 1: Buat permintaan yang dikanonisasi.
        const canonicalQueryString = Object.entries(signRequest.queryParam)
            .sort(([a], [b]) => a.localeCompare(b))
            .map(([key, value]) => `${percentCode(key)}=${percentCode(value)}`)
            .join('&');

        // Body permintaan. Jika body permintaan kosong, seperti dalam permintaan GET, gunakan string kosong untuk muatan.
        const requestPayload = signRequest.body || encoder.encode('');
        const hashedRequestPayload = sha256Hex(requestPayload);
        signRequest.headers['x-acs-content-sha256'] = hashedRequestPayload;
        if (securityToken) {
            signRequest.headers['x-acs-security-token'] = securityToken;
        }

        // Konversi semua nama header menjadi huruf kecil.
        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();
        // Daftar header yang ditandatangani. Nama header dalam huruf kecil, diurutkan secara alfabetis, dan dipisahkan oleh titik koma (;).
        const signedHeaders = sortedKeys.join(";")
        // Header yang dikanonisasi. Gabungkan beberapa header yang dikanonisasi, diurutkan secara alfabetis berdasarkan nama header huruf kecil.
        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);

        // Langkah 2: Buat string-to-sign.
        const hashedCanonicalRequest = sha256Hex(encoder.encode(canonicalRequest));
        const stringToSign = `${ALGORITHM}\n${hashedCanonicalRequest}`;
        console.log('stringToSign=========>', stringToSign);

        // Langkah 3: Hitung signature.
        const signature = hmac256(accessKeySecret, stringToSign);
        console.log('signature=========>', signature);

        // Langkah 4: Buat header Authorization.
        const authorization = `${ALGORITHM} Credential=${accessKeyId},SignedHeaders=${signedHeaders},Signature=${signature}`;
        console.log('authorization=========>', authorization);
        signRequest.headers['Authorization'] = authorization;
    } catch (error) {
        console.error('Gagal mendapatkan authorization');
        console.error(error);
    }
}

async function callApi(signRequest) {
    try {
        let url = `https://${signRequest.host}${signRequest.canonicalUri}`;
        // Tambahkan parameter permintaan.
        if (signRequest.queryParam) {
            const query = new URLSearchParams(signRequest.queryParam);
            url += '?' + query.toString();
        }
        console.log('url=========>', url);

        // Konfigurasikan opsi permintaan.
        let options = {
            method: signRequest.httpMethod.toUpperCase(),
            headers: signRequest.headers
        };

        // Tangani body permintaan.
        if (signRequest.body && ['POST', 'PUT'].includes(signRequest.httpMethod.toUpperCase())) {
            options.body = signRequest.body;
        }
        return (await fetch(url, options)).text();
    } catch (error) {
        console.error('Gagal mengirim permintaan:', 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) {
    // Jika nilainya null, tidak perlu pemrosesan lebih lanjut.
    if (value === null) {
        return;
    }
    if (key === null) {
        key = "";
    }

    // Jika nilainya array, iterasi melalui elemennya dan proses secara rekursif.
    if (Array.isArray(value)) {
        value.forEach((item, index) => {
            processObject(map, `${key}.${index + 1}`, item);
        });
    } else if (typeof value === 'object' && value !== null) {
        // Jika nilainya objek, iterasi melalui pasangan kunci-nilainya dan proses secara rekursif.
        Object.entries(value).forEach(([subKey, subValue]) => {
            processObject(map, `${key}.${subKey}`, subValue);
        });
    } else {
        // Jika kuncinya dimulai dengan titik (.), hapus untuk menjaga jalur kunci tetap kontinu.
        if (key.startsWith('.')) {
            key = key.slice(1);
        }
        map[key] = String(value);
    }
}

/**
 * Ini adalah contoh signature. Anda harus mengganti parameter contoh dalam fungsi main dengan nilai aktual Anda.
 * API ROA dan API RPC hanya berbeda dalam logika untuk membangun canonicalUri. Sisa prosesnya sama.
 *
 * Dapatkan metode permintaan (method), nama parameter (name), tipe parameter (type), dan lokasi parameter (in) dari metadata API, lalu kapsulkan parameter ini dalam objek Request.
 * 1. Jika lokasi parameter adalah "in":"query" dalam metadata API, lewatkan dengan menggunakan <code>queryParam
Content-Typeapplication/x-www-form-urlencodedContent-Typeapplication/octet-streamapplication/jsonapplication/jsonContent-Typeapplication/x-www-form-urlencodedRegionIdStringInstanceIdArray"in": "body"ContextStringFormatTypeSourceLanguageTargetLanguage"in": "body"canonicalUripercentCode()canonicalUripercentCode()

PHP

Catatan

Dalam contoh ini, lingkungan runtime PHP 7.4.33 digunakan. Sesuaikan parameter berdasarkan kebutuhan bisnis Anda.

<?php

class SignatureDemo
{
    // Algoritma enkripsi
    private $ALGORITHM;
    // ID Access Key
    private $AccessKeyId;
    // Rahasia Access Key
    private $AccessKeySecret;

    private $SecurityToken;

    public function __construct()
    {
        date_default_timezone_set('UTC'); // Atur zona waktu ke UTC.
        $this->AccessKeyId = getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'); // Mendapatkan ID Access Key Pengguna RAM dari variabel lingkungan.
        $this->AccessKeySecret = getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'); // Mendapatkan rahasia Access Key Pengguna RAM dari variabel lingkungan.
        $this->SecurityToken = getenv('ALIBABA_CLOUD_SECURITY_TOKEN');
        $this->ALGORITHM = 'ACS3-HMAC-SHA256'; // Mengatur algoritma enkripsi.
    }

    /**
     * Contoh ini menunjukkan penandatanganan API. Anda harus mengganti parameter contoh dalam metode utama dengan nilai Anda.
     * API ROA dan RPC hanya berbeda dalam logika untuk nilai canonicalUri. Sisa prosesnya serupa.
     *
     * Dapatkan metode permintaan (methods), nama parameter permintaan (name), tipe parameter (type), dan lokasi parameter (in) dari metadata API, lalu kapsulkan parameter dalam SignatureRequest.
     * 1. Jika lokasi parameter permintaan didefinisikan sebagai "in":"query" dalam metadata API, lewatkan menggunakan queryParam. Catatan: Untuk API RPC, Anda juga dapat melewatkan tipe parameter ini dalam body dengan content-type application/x-www-form-urlencoded. Lihat Contoh 3.
     * 2. Jika lokasi parameter permintaan didefinisikan sebagai "in": "body" dalam metadata API, lewatkan dalam body dengan tipe MIME application/octet-stream atau application/json. Untuk API RPC, application/json tidak disarankan. Gunakan metode dalam Contoh 3 sebagai gantinya.
     * 3. Jika lokasi parameter permintaan didefinisikan sebagai "in": "formData" dalam metadata API, lewatkan dalam body dengan tipe MIME application/x-www-form-urlencoded.
     */
    public function main()
    {
        // Contoh 1: Permintaan API RPC dengan parameter "in": "query".
        $request = $this->createRequest('POST', '/', 'ecs.cn-hangzhou.aliyuncs.com', 'DescribeInstanceStatus', '2014-05-26');
        // Parameter permintaan untuk DescribeInstanceStatus adalah sebagai berikut:
        $request['queryParam'] = [
            // Dalam metadata API, RegionId adalah String wajib dalam kueri.
            'RegionId' => 'cn-hangzhou',
            // Dalam metadata API, InstanceId adalah array opsional dalam kueri.
            'InstanceId' => ["i-bp11ht4h2kdXXXXXXXX", "i-bp16maz3h3xgXXXXXXXX", "i-bp10r67hmslXXXXXXXX"]
        ];

        // // Contoh 2: Permintaan API RPC dengan parameter "in": "body" (skenario unggah file).
        // $request = $this->createRequest('POST', '/', 'ocr-api.cn-hangzhou.aliyuncs.com', 'RecognizeGeneral', '2021-07-07');
        // // Parameter permintaan ditentukan sebagai "in": "body" dalam metadata API. Lewatkan parameter dalam body.
        // $filePath = 'D:\\test.png';
        // // Gunakan sumber daya file untuk melewatkan file biner.
        // $fileResource = fopen($filePath, 'rb');
        // $request['body'] = stream_get_contents($fileResource); 
        // $request['headers']['content-type'] = 'application/octet-stream'; // Mengatur Content-Type ke application/octet-stream.
        // // Tutup sumber daya file.
        // fclose($fileResource);

        // // Contoh 3: Permintaan API RPC dengan parameter "in": "formData" atau "in": "body" (skenario bukan unggah file).
        // $request = $this->createRequest('POST', '/', 'mt.aliyuncs.com', 'TranslateGeneral', '2018-10-12');
        // // Parameter permintaan untuk TranslateGeneral adalah sebagai berikut:
        // $request['queryParam'] = [
        //     // Dalam metadata API, Context adalah String opsional dalam kueri.
        //     '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';

        // // Permintaan POST API ROA
        // $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'; 

        // // Permintaan GET API ROA
        // // Jika canonicalUri berisi parameter path, encode parameter path menggunakan rawurlencode({parameter_path}).
        // $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,
        // ];

        // // Permintaan DELETE API ROA
        // $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);
        // Panggil API.
        $this->callApi($request);
    }

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

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

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

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

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

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

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

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

    private function callApi($request)
    {
        try {
            // Kirim permintaan menggunakan cURL.
            $url = "https://" . $request['host'] . $request['canonicalUri'];

            // Tambahkan parameter permintaan ke URL.
            if (!empty($request['queryParam'])) {
                $url .= '?' . http_build_query($request['queryParam']);
            }

            echo $url;
            // Inisialisasi sesi cURL.
            $ch = curl_init();

            // Atur opsi cURL.
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Menonaktifkan verifikasi sertifikat SSL. Ini tidak aman dan tidak disarankan untuk lingkungan produksi.
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Mengembalikan respons sebagai string alih-alih menampilkannya langsung.
            curl_setopt($ch, CURLOPT_HTTPHEADER, $this->convertHeadersToArray($request['headers'])); // Menambahkan header permintaan.

            // Atur opsi cURL berdasarkan tipe permintaan.
            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 "Metode HTTP tidak didukung: " . $request['body'];
                    throw new Exception("Metode HTTP tidak didukung");
            }

            // Kirim permintaan.
            $result = curl_exec($ch);

            // Periksa kesalahan.
            if (curl_errno($ch)) {
                echo "Gagal mengirim permintaan: " . curl_error($ch);
            } else {
                echo $result;
            }

        } catch (Exception $e) {
            echo "Error: " . $e->getMessage();
        } finally {
            // Tutup sesi cURL.
            curl_close($ch);
        }
    }

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

    function processObject($value)
    {
        // Jika nilainya null, kembalikan.
        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);
        // Bangun dan encode parameter kueri.
        $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)
    {
        // Urutkan header berdasarkan kunci dan gabungkan.
        uksort($headers, 'strcasecmp');
        $canonicalHeaders = '';
        foreach ($headers as $key => $value) {
            $canonicalHeaders .= strtolower($key) . ':' . trim($value) . "\n";
        }
        return $canonicalHeaders;
    }

    private function buildSignedHeaders($headers)
    {
        // Bangun string header yang ditandatangani.
        $signedHeaders = array_keys($headers);
        sort($signedHeaders, SORT_STRING | SORT_FLAG_CASE);
        return implode(';', array_map('strtolower', $signedHeaders));
    }
}

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

.NET

Catatan

Dalam contoh ini, lingkungan runtime .NET 8.0.302 digunakan. Sesuaikan parameter berdasarkan kebutuhan bisnis Anda.

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

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

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

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

    public class Program
    {
        private static readonly string AccessKeyId = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID") ?? throw new InvalidOperationException("Variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_ID belum diatur.");
        private static readonly string AccessKeySecret = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET") ?? throw new InvalidOperationException("Variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_SECRET belum diatur.");
        private static readonly string? SecurityToken = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_SECURITY_TOKEN");
        private const string Algorithm = "ACS3-HMAC-SHA256";
        private const string ContentType = "content-type";

        /**
        * Contoh ini menunjukkan cara membuat signature permintaan. Anda harus mengganti parameter contoh dalam metode utama dengan nilai aktual Anda.
        * API ROA dan RPC hanya berbeda dalam logika untuk menentukan nilai canonicalUri. Sisa prosesnya serupa.
        *
        * Dari metadata API, dapatkan metode permintaan, dan nama, tipe, serta lokasi (`in`) setiap parameter permintaan. Lalu, kapsulkan informasi ini dalam objek Request.
        * 1. Jika lokasi parameter permintaan adalah "in":"query" dalam metadata, lewatkan dengan menggunakan `QueryParam`. Catatan: Untuk API RPC, Anda juga dapat melewatkan parameter ini dalam body permintaan dengan content-type application/x-www-form-urlencoded. Lihat Contoh 3.
        * 2. Jika lokasi parameter permintaan adalah "in": "body" dalam metadata, lewatkan dalam body permintaan sebagai `application/octet-stream` atau `application/json`. Untuk API RPC, kami tidak menyarankan menggunakan `application/json`; gunakan `application/x-www-form-urlencoded` (seperti yang ditunjukkan dalam Contoh 3) sebagai gantinya.
        * 3. Jika lokasi parameter permintaan adalah "in": "formData" dalam metadata, lewatkan dalam body permintaan sebagai `application/x-www-form-urlencoded`.
        */
        public static void Main(string[] args)
        {
            // Contoh 1: Permintaan API RPC dengan parameter "in":"query"
            string httpMethod = "POST"; // Metode permintaan. Sebagian besar API RPC mendukung POST dan GET. Contoh ini menggunakan POST.
            string canonicalUri = "/"; // API RPC tidak memiliki path resource, jadi garis miring (/) digunakan sebagai `CanonicalUri`.
            string host = "ecs.cn-hangzhou.aliyuncs.com"; // Endpoint layanan cloud.
            string xAcsAction = "DescribeInstanceStatus"; // Operasi API.
            string xAcsVersion = "2014-05-26"; // Versi API.
            var request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
            // Parameter permintaan untuk `DescribeInstanceStatus`:
            // `RegionId` adalah parameter string wajib yang berada di kueri.
            request.QueryParam["RegionId"] = "cn-hangzhou"; 
            // `InstanceId` adalah parameter array opsional yang berada di kueri.
            List<string> instanceIds = ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"];
            request.QueryParam["InstanceId"] = instanceIds; 

            // // Contoh 2: Permintaan API RPC dengan parameter "in":"body" (unggah file)
            // 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);
            // // Parameter permintaan berada di "body" dalam metadata. Lewatkan parameter dalam body permintaan.
            // request.Body = File.ReadAllBytes(@"D:\test.png");
            // request.Headers["content-type"] = "application/octet-stream";

            // // Contoh 3: Permintaan API RPC dengan parameter "in":"formData" atau "in":"body" (bukan unggah file)
            // 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);
            // // Parameter permintaan untuk TranslateGeneral adalah sebagai berikut:
            // // Dalam metadata, Context adalah String, berada di "query", dan opsional.
            // request.QueryParam["Context"] = "Morning"; 
            // // Dalam metadata, parameter seperti FormatType, SourceLanguage, dan TargetLanguage berada di "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";

            // // Contoh: Permintaan POST API ROA
            // 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);
            // // Body permintaan. Gunakan JsonConvert untuk mengonversi body menjadi string 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";

            // // Contoh: Permintaan GET API ROA
            // String httpMethod = "GET";
            // // Jika canonicalUri berisi parameter path, Anda harus meng-encode parameter path dengan menggunakan `percentCode({parameter_path})`.
            // 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;

            // // Contoh: Permintaan DELETE API ROA
            // String httpMethod = "DELETE";
            // // Jika canonicalUri berisi parameter path, Anda harus meng-encode parameter path dengan menggunakan `percentCode({parameter_path})`.
            // 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);
            // Panggil API.
            var result = CallApiAsync(request);
            Console.WriteLine($"result:{result.Result}");
        }

        private static async Task<string?> CallApiAsync(Request request)
        {
            try
            {
                // Deklarasikan instance HttpClient.
                using var httpClient = new HttpClient();

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

                // Tambahkan parameter permintaan.
                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,
                };

                // Atur header permintaan.
                foreach (var entry in request.Headers)
                {
                    if (entry.Key == "Authorization")
                    {
                        requestMessage.Headers.TryAddWithoutValidation("Authorization", entry.Value.ToString()); ;
                    }
                    else if (entry.Key == ContentType) // Header `content-type` diatur dengan konten body permintaan nanti.
                    {
                        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;
                }
                
                // Kirim permintaan.
                HttpResponseMessage response = await httpClient.SendAsync(requestMessage);
                // Baca konten respons.
                string result = await response.Content.ReadAsStringAsync();
                return result;
            }
            catch (UriFormatException e)
            {
                Console.WriteLine("Sintaks URI tidak valid");
                Console.WriteLine(e.Message);
                return null;
            }
            catch (Exception e)
            {
                Console.WriteLine("Gagal mengirim permintaan");
                Console.WriteLine(e);
                return null;
            }
        }

        private static void GetAuthorization(Request request)
        {
            try
            {
                // Meratakan parameter `List` atau `Dictionary` dalam `QueryParam`.
                request.QueryParam = FlattenDictionary(request.QueryParam);

                // Langkah 1: Bangun permintaan kanonis.
                StringBuilder canonicalQueryString = new();
                foreach (var entry in request.QueryParam.OrderBy(e => e.Key.ToLower()))
                {
                    if (canonicalQueryString.Length > 0)
                    {
                        canonicalQueryString.Append('&');
                    }
                    canonicalQueryString.Append($"{PercentCode(entry.Key)}={PercentCode(entry.Value?.ToString() ?? "")}");
                }

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

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

                // Langkah 2: Bangun string-to-sign.
                string hashedCanonicalRequest = Sha256Hash(Encoding.UTF8.GetBytes(canonicalRequest));
                string stringToSign = $"{Algorithm}\n{hashedCanonicalRequest}";
                Console.WriteLine($"stringToSign:{stringToSign}");

                // Langkah 3: Hitung signature.
                string signature = HmacSha256(AccessKeySecret, stringToSign);

                // Langkah 4: Bangun header Authorization.
                string authorization = $"{Algorithm} Credential={AccessKeyId},SignedHeaders={signedHeaders},Signature={signature}";
                request.Headers["Authorization"] = authorization;
                Console.WriteLine($"authorization:{authorization}");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Gagal mendapatkan authorization");
                Console.WriteLine(ex.Message);
            }
        }

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

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

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

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

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

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

        private static string PercentCode(string str)
        {
            if (string.IsNullOrEmpty(str))
            {
                throw new ArgumentException("String input tidak boleh null atau kosong.");
            }
            return Uri.EscapeDataString(str).Replace("+", "%20").Replace("*", "%2A").Replace("%7E", "~");
        }
    }
}

Rust

Catatan

Dalam contoh ini, lingkungan runtime rustc 1.82.0 digunakan. Sesuaikan parameter berdasarkan kebutuhan bisnis Anda.

Untuk menggunakan metode signature di Rust, Anda harus menambahkan dependensi berikut ke file 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"
Cow<str>String&strmainin"in":"kueri"query_paramsContent-Typeapplication/x-www-form-urlencoded"in": "body"application/octet-streamapplication/jsonapplication/json"in": "formData"application/x-www-form-urlencoded dengan jalur file yang sebenarnya. // // Saat badan permintaan bertipe biner. // let body = RequestBody::Binary(binary_data); // // Parameter kueri kosong. // let query_params = &[]; // Contoh API RPC 3: Permintaan dengan parameter "in":"formData" atau "in":"body" untuk pengunggahan non-file (POST) // let method = Method::POST; // let host = "mt.aliyuncs.com"; // let canonical_uri = "/"; // let action = "TranslateGeneral"; // let version = "2018-10-12"; // // Parameter seperti FormatType, SourceLanguage, dan TargetLanguage ditentukan sebagai "in":"formData" dalam metadata API. // let mut form_data = HashMap::new(); // Tipe badan permintaan adalah FormData(HashMap<String, FormValue>). FormValue mendukung Vec<String>, HashMap<String, String>, dll. Anda dapat menambahkan tipe lain ke enum FormValue sesuai kebutuhan. // form_data.insert(String::from("FormatType"),FormValue::String(String::from("text"))); // form_data.insert(String::from("SourceLanguage"),FormValue::String(String::from("zh"))); // form_data.insert(String::from("TargetLanguage"),FormValue::String(String::from("en"))); // form_data.insert(String::from("SourceText"),FormValue::String(String::from("Hello"))); // form_data.insert(String::from("Scene"),FormValue::String(String::from("general"))); // let query_params = &[("Context", "morning")]; // let body = RequestBody::FormData(form_data); // Permintaan POST API ROA: CreateCluster // // Tentukan detail permintaan API. // let method = Method::POST; // let host = "cs.cn-hangzhou.aliyuncs.com"; // let canonical_uri = "/clusters"; // let action = "CreateCluster"; // let version = "2015-12-15"; // // Atur parameter badan permintaan. // let mut body_json = HashMap::new(); // Tipe badan permintaan adalah Json(HashMap<String, Value>). Value mendukung tipe seperti String, Number, Boolean, Null, Array, dan objek JSON bersarang. // body_json.insert(String::from("name"),json!("Test-Cluster")); // body_json.insert(String::from("region_id"),json!("cn-hangzhou")); // body_json.insert(String::from("cluster_type"),json!("ExternalKubernetes")); // body_json.insert(String::from("vpcid"),json!("vpc-2zeou1uodXXXXXXXX")); // body_json.insert(String::from("container_cidr"),json!("10.X.X.X/X")); // body_json.insert(String::from("service_cidr"),json!("10.X.X.X/X")); // body_json.insert(String::from("security_group_id"),json!("sg-2ze1a0rlgXXXXXXXX")); // body_json.insert( // String::from("vswitch_ids"), // Value::Array(vec![ // Value::from("vsw-2zei30dhflXXXXXXXX"), // Value::from("vsw-2zei30dhflXXXXXXXX"), // Value::from("vsw-2zei30dhflXXXXXXXX"), // ]), // ); // // Parameter kueri kosong. // let query_params = &[]; // let body = RequestBody::Json(body_json); // Permintaan GET API ROA: DescribeClusterResources // let method = Method::GET; // let host = "cs.cn-hangzhou.aliyuncs.com"; // // Bangun jalur sumber daya. // let uri = format!("/clusters/{}/resources", percent_code("ce196d21571a64be9XXXXXXXX").as_ref()); // let canonical_uri = uri.as_str(); // Jalur sumber daya dalam tipe &str. // let action = "DescribeClusterResources"; // let version = "2015-12-15"; // // Atur parameter kueri. // let query_params = &[("with_addon_resources", if true { "true" } else { "false" })]; // Dapat berupa "true" atau "false". // // Atur badan permintaan menjadi kosong. // let body = RequestBody::None; // Permintaan DELETE API ROA: Hapus kluster pay-as-you-go // let method = Method::DELETE; // let host = "cs.cn-hangzhou.aliyuncs.com"; // let uri = format!("/clusters/{}", percent_code("ce0138ff31ad044f8XXXXXXXX").as_ref()); // let canonical_uri = uri.as_str(); // Jalur sumber daya dalam tipe &str. // let action = "DeleteCluster"; // let version = "2015-12-15"; // // Parameter kueri kosong. // let query_params = &[]; // // Badan permintaan kosong. // let body = RequestBody::None; // Contoh API RPC: SendSms // let method = Method::POST; // let host = "dysmsapi.aliyuncs.com"; // let canonical_uri = "/"; // API RPC tidak memiliki jalur sumber daya, sehingga gunakan garis miring (/) sebagai CanonicalURI. // let action = "SendSms"; // let version = "2017-05-25"; // let mut query: Vec<(&str, &str)> = Vec::new(); // query.push(("PhoneNumbers", "<YOUR_PHONENUMBERS>")); // query.push(("TemplateCode", "<YOUR_TEMPLATECODE>")); // query.push(("SignName", "<YOUR_SIGNNAME>")); // query.push(("TemplateParam", "<YOUR_TEMPLATEPARAM>")); // // Parameter kueri. // let query_params: &[(&str, &str)] = &query // // Badan permintaan kosong. // let body = RequestBody::None; // Kirim permintaan. match call_api( client.clone(), method, // metode permintaan: POST, GET, atau DELETE host, canonical_uri, query_params, // parameter kueri ("in":"query") action, version, body, // badan permintaan ("in":"body"), mendukung tipe JSON, FormData, dan biner access_key_id, access_key_secret, ) .await { Ok(response) => println!("Respons: {}", response), Err(error) => eprintln!("Error: {}", error), } } use core::str;
use std::collections::{BTreeMap, HashMap};
use std::env;
use std::time::{SystemTime, SystemTimeError};
use chrono::DateTime;
use hmac::{Hmac, Mac};
use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
use rand::Rng;
use serde_json::{json, Value}; 
use std::borrow::Cow;    
use reqwest::{
    Client,
    header::{HeaderMap, HeaderValue}, Method, Response, StatusCode,
};
use sha2::{Digest, Sha256};
use base64::engine::general_purpose::STANDARD;
use base64::Engine;

// Menghasilkan timestamp x-acs-date.
pub fn current_timestamp() -> Result<u64, SystemTimeError> {
    Ok(SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)?
        .as_secs())
}
// Meng-encode persen string.
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) // Mengembalikan <code>Cow<str>

Shell

#!/bin/bash

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

# Parameter permintaan -- Sesuaikan parameter permintaan berdasarkan kebutuhan bisnis Anda.
httpMethod="POST"
host="dns.aliyuncs.com"
queryParam=("DomainName=example.com" "RRKeyWord=@")
action="DescribeDomainRecords"
version="2015-01-09"
canonicalURI="/"
# Parameter tipe body atau formData dapat ditentukan dalam body.
# Jika parameternya body, nilai body adalah string JSON: "{'key1':'value1','key2':'value2'}". Anda perlu menambahkan content-type:application/json; charset=utf-8 ke header yang digunakan untuk perhitungan signature.
# Jika parameternya file biner, Anda tidak perlu memodifikasi body. Tambahkan content-type:application/octet-stream ke header yang digunakan untuk perhitungan signature, dan tambahkan --data-binary ke variabel curl_command.
# Jika parameternya formData, tentukan parameter body dalam format "key1=value1&key2=value2", dan tambahkan content-type:application/x-www-form-urlencoded ke header yang digunakan untuk perhitungan signature.
body=""

# Tentukan waktu dalam standar ISO 8601 dalam UTC.
utc_timestamp=$(date +%s)
utc_date=$(date -u -d @${utc_timestamp} +"%Y-%m-%dT%H:%M:%SZ") 
# Atur header x-acs-signature-nonce ke bilangan acak.
random=$(uuidgen | sed 's/-//g') 

# Tambahkan header permintaan yang digunakan untuk perhitungan signature.
headers="host:${host}
x-acs-action:${action}
x-acs-version:${version}
x-acs-date:${utc_date}
x-acs-signature-nonce:${random}"

# Fungsi encoding 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}"
}

# Langkah 1: Buat permintaan yang dikanonisasi.
# Ratakan semua parameter kueri.
newQueryParam=()

# Telusuri setiap parameter kueri mentah.
for param in "${queryParam[@]}"; do
    # Periksa apakah nilai parameter kueri berisi tanda sama dengan (=). Jika nilai parameter berisi tanda sama dengan (=), nilai parameter adalah pasangan kunci-nilai.
    if [[ "$param" == *"="* ]]; then
        # Pisahkan nilai parameter menjadi kunci dan nilai.
        IFS='=' read -r key value <<< "$param"

        # Encode nilai dalam format URL.
        value=$(urlencode "$value")

        # Periksa apakah nilainya daftar. Jika nilainya daftar, nilainya dalam tanda kurung ().
        if [[ "$value" =~ ^\(.+\)$ ]]; then
            # Hapus tanda kurung ().
            value="${value:1:-1}"

            # Gunakan pemisah bidang internal (IFS) khusus untuk memisahkan nilai.
            IFS=' ' read -ra values <<< "$value"

            # Tambahkan indeks untuk setiap nilai.
            index=1
            for val in "${values[@]}"; do
                # Hapus tanda kutip ganda (").
                val="${val%\"}"
                val="${val#\"}"

                # Tambahkan parameter kueri ke array baru.
                newQueryParam+=("$key.$index=$val")
                ((index++))
            done
        else
            # Jika nilainya bukan daftar, tambahkan parameter kueri ke array baru.
            newQueryParam+=("$key=$value")
        fi
    else
        # Jika nilainya tidak dalam tanda kurung, tambahkan parameter kueri ke array baru tanpa modifikasi.
        newQueryParam+=("$param")
    fi
done

# Proses dan urutkan parameter kueri baru.
sortedParams=()
declare -A paramsMap
for param in "${newQueryParam[@]}"; do
    IFS='=' read -r key value <<< "$param"
    paramsMap["$key"]="$value"
done
# Urutkan parameter kueri berdasarkan kunci.
for key in $(echo ${!paramsMap[@]} | tr ' ' '\n' | LC_ALL=C sort); do
    sortedParams+=("$key=${paramsMap[$key]}")
done

#1.1 Buat string kueri yang dikanonisasi.
canonicalQueryString=""
first=true
for item in "${sortedParams[@]}"; do
    [ "$first" = true ] && first=false || canonicalQueryString+="&"
    # Periksa apakah tanda sama dengan (=) ada.
    if [[ "$item" == *=* ]]; then
        canonicalQueryString+="$item"
    else
        canonicalQueryString+="$item="
    fi
done

# 1.2 Proses body permintaan.
hashedRequestPayload=$(echo -n "$body" | openssl dgst -sha256 | awk '{print $2}')
headers="${headers}
x-acs-content-sha256:$hashedRequestPayload"

# 1.3 Buat header yang dikanonisasi.
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 Buat permintaan yang dikanonisasi.
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}')
# Langkah 2: Buat string-to-sign.
stringToSign="${algorithm}\n${hashedCanonicalRequest}"
echo -e "stringToSign=$stringToSign"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++++"

# Langkah 3: Hitung string signature.
signature=$(printf "${stringToSign}" | openssl dgst -sha256 -hmac "${accessKey_secret}" | sed 's/^.* //')
echo -e "signature=${signature}"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++++"

# Langkah 4: Tentukan header Authorization.
authorization="${algorithm} Credential=${accessKey_id},SignedHeaders=${signedHeaders},Signature=${signature}"
echo -e "authorization=${authorization}"

# Bangun perintah cURL.
url="https://$host$canonicalURI"
curl_command="curl -X $httpMethod '$url?$canonicalQueryString'"

# Tambahkan header Authorization.
IFS=$'\n'  # Gunakan line feed (\n) sebagai IFS baru.
for header in $headers; do
    curl_command="$curl_command -H '$header'"
done
curl_command+=" -H 'Authorization:$authorization'"
# Jika body adalah file biner, komentari baris kode berikut.
curl_command+=" -d '$body'"
# Jika body adalah file biner, hapus komentar baris kode berikut.
#curl_command+=" --data-binary @"/root/001.png" "

echo "$curl_command"
# Jalankan perintah cURL.
eval "$curl_command"

Bahasa 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>

// Mengambil ID Access Key dan Rahasia Access Key dari variabel lingkungan.
#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

// Pasangan kunci-nilai untuk pengurutan.
typedef struct {
    char key[256];
    char value[256];
} KeyValuePair;

// Fungsi perbandingan untuk mengurutkan kunci secara leksikografis.
int compare_pairs(const void *a, const void *b) {
    return strcmp(((const KeyValuePair *)a)->key, ((const KeyValuePair *)b)->key);
}

// Meng-encode URL string.
char* percentEncode(const char* str) {
    if (str == NULL) {
        fprintf(stderr, "String input tidak boleh null\n");
        return NULL;
    }
    size_t len = strlen(str);
    char* encoded = (char*)malloc(len * 3 + 1);
    if (encoded == NULL) {
        fprintf(stderr, "Gagal mengalokasikan memori\n");
        free(encoded); 
        return NULL;
    }
    char* ptr = encoded;
    for (size_t i = 0; i < len; i++) {
        unsigned char c = (unsigned char)str[i];
        if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
            *ptr++ = c;
        } else {
            ptr += sprintf(ptr, "%%%02X", c);
        }
    }
    *ptr = '\0'; 
    char* finalEncoded = malloc(strlen(encoded) + 1);
    if (finalEncoded) {
        char* fptr = finalEncoded;
        for (size_t j = 0; j < strlen(encoded); j++) {
            if (encoded[j] == '+') {
                strcpy(fptr, "%20");
                fptr += 3; 
            } else if (encoded[j] == '*') {
                strcpy(fptr, "%2A");
                fptr += 3;
            } else if (encoded[j] == '~') {
                *fptr++ = '~';
            } else {
                *fptr++ = encoded[j];
            }
        }
        *fptr = '\0'; 
    }

    free(encoded); 
    return finalEncoded;
}

/**
 * @brief Meng-encode URL dan mengurutkan parameter kueri untuk menghasilkan string kueri yang dikanonisasi.
 * @param query_params String parameter kueri asli, misalnya, "key1=value1&key2=value2".
 * @return char* Pointer ke string kueri yang dikanonisasi. Pemanggil harus membebaskan memori yang dialokasikan.
 */
char* generate_sorted_encoded_query(const char* query_params) {
    if (query_params == NULL || strlen(query_params) == 0) {
        return strdup(""); // Jika parameter kosong, kembalikan string kosong.
    }

    KeyValuePair pairs[100]; // Mendukung maksimal 100 pasangan kunci-nilai.
    int pair_count = 0;

    char* copy = strdup(query_params);
    if (!copy) {
        fprintf(stderr, "Gagal mengalokasikan memori\n");
        return NULL;
    }

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

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

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

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

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

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

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

    free(copy);

    // Urutkan berdasarkan kunci.
    qsort(pairs, pair_count, sizeof(KeyValuePair), compare_pairs);

    // Gabungkan parameter kueri yang diurutkan.
    char* query_sorted = malloc(BUFFER_SIZE);
    if (!query_sorted) {
        fprintf(stderr, "Gagal mengalokasikan memori\n");
        return NULL;
    }
    query_sorted[0] = '\0';

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

    return query_sorted;
}

// Menghitung hash HMAC-SHA256.
void hmac256(const char *key, const char *message, char *output) {
    unsigned char hmac[SHA256_DIGEST_LENGTH];
    unsigned int result_len;
    HMAC(EVP_sha256(), key, strlen(key), (unsigned char *)message, strlen(message), hmac, &result_len);
    for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
        sprintf(output + (i * 2), "%02x", hmac[i]);
    }
    output[SHA256_DIGEST_LENGTH * 2] = '\0';
}
// Menghitung hash 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';
}
// Menghasilkan x-acs-signature-nonce.
void generate_uuid(char *uuid, size_t size) {
    if (size < 37) { 
        fprintf(stderr, "Ukuran buffer terlalu kecil untuk UUID\n");
        return;
    }
    unsigned char random_bytes[16];
    RAND_bytes(random_bytes, sizeof(random_bytes));
    random_bytes[6] &= 0x0f; // Simpan 4 bit tinggi.
    random_bytes[6] |= 0x40; // Atur versi ke 4.
    random_bytes[8] &= 0x3f; // Simpan 2 bit tinggi.
    random_bytes[8] |= 0x80; // Atur varian ke 10xx.
    snprintf(uuid, size,
             "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
             random_bytes[0], random_bytes[1], random_bytes[2], random_bytes[3],
             random_bytes[4], random_bytes[5], random_bytes[6], random_bytes[7],
             random_bytes[8], random_bytes[9], random_bytes[10], random_bytes[11],
             random_bytes[12], random_bytes[13], random_bytes[14], random_bytes[15]);
}
// Membaca file.
size_t read_file(const char *file_path, char **buffer) {
    FILE *file = fopen(file_path, "rb");
    if (!file) {
        fprintf(stderr, "Tidak dapat membuka file %s\n", file_path);
        return 0; // Baca gagal.
    }
    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, "Gagal mengalokasikan memori untuk buffer file\n");
        fclose(file);
        return 0; // Baca gagal.
    }
    fread(*buffer, 1, file_size, file);
    fclose(file);
    return file_size; // Mengembalikan jumlah byte yang dibaca.
}
// Menghitung header Authorization.
char* get_authorization(const char *http_method, const char *canonical_uri, const char *host,
                       const char *x_acs_action, const char *x_acs_version, const char *query_params,
                       const char *body, char *authorization_header,
                        char *hashed_payload, char *x_acs_date, char *uuid) {
    // Siapkan x-acs-signature-nonce, x-acs-date, x-acs-content-sha256, dan string-to-sign.
    generate_uuid(uuid, 37);
    // Format x-acs-date adalah yyyy-MM-ddTHH:mm:ssZ, seperti 2025-04-17T07:19:10Z.
    time_t now = time(NULL);
    struct tm *utc_time = gmtime(&now);
    strftime(x_acs_date, 64, "%Y-%m-%dT%H:%M:%SZ", utc_time);
    // string-to-sign.
    char signed_headers[] = "host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version";
    // x-acs-content-sha256 
    sha256_hex(body ? body : "", hashed_payload);
    printf("Generated x-acs-content-sha256: %s\n", hashed_payload);
    // 1. Buat header permintaan yang dikanonisasi.
    char canonical_headers[BUFFER_SIZE];
    snprintf(canonical_headers, sizeof(canonical_headers),
             "host:%s\nx-acs-action:%s\nx-acs-content-sha256:%s\nx-acs-date:%s\nx-acs-signature-nonce:%s\nx-acs-version:%s",
              host, x_acs_action, hashed_payload, x_acs_date, uuid, x_acs_version);
    printf("Canonical Headers:\n%s\n", canonical_headers);

    // 2. Buat permintaan kanonis.
    // Urutkan dan encode parameter kueri.
    char* sorted_query_params = generate_sorted_encoded_query(query_params);
    if (!sorted_query_params) {
      fprintf(stderr, "Gagal menghasilkan string kueri yang diurutkan\n");
      return NULL;
    }
    char canonical_request[BUFFER_SIZE];
    snprintf(canonical_request, sizeof(canonical_request),
         "%s\n%s\n%s\n%s\n\n%s\n%s",
         http_method,
         canonical_uri,
         sorted_query_params ? sorted_query_params : "",
         canonical_headers,
         signed_headers,
         hashed_payload);
    printf("Canonical Request:\n%s\n", canonical_request);

    // 3. Hitung hash SHA-256 dari permintaan kanonis.
    char hashed_canonical_request[SHA256_DIGEST_LENGTH * 2 + 1];
    sha256_hex(canonical_request, hashed_canonical_request);
    printf("hashedCanonicalRequest: %s\n", hashed_canonical_request);
    // 4. Buat string-to-sign. 
    char string_to_sign[BUFFER_SIZE];
    snprintf(string_to_sign, sizeof(string_to_sign), "%s\n%s", ALGORITHM, hashed_canonical_request);
    printf("stringToSign:\n%s\n", string_to_sign);
    // 5. Hitung signature. 
    char signature[SHA256_DIGEST_LENGTH * 2 + 1];
    hmac256(ACCESS_KEY_SECRET, string_to_sign, signature);
    printf("Signature: %s\n", signature);
    // 6. Buat header Authorization.
    snprintf(authorization_header, BUFFER_SIZE,
             "%s Credential=%s,SignedHeaders=%s,Signature=%s",
             ALGORITHM, ACCESS_KEY_ID, signed_headers, signature);
    printf("Authorization: %s\n", authorization_header);

    return sorted_query_params;
}
// Mengirim permintaan API.
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) {
    // Variabel untuk perhitungan signature.
    char authorization_header[BUFFER_SIZE];
    char hashed_payload[SHA256_DIGEST_LENGTH * 2 + 1];
    char x_acs_date[64];
    char uuid[37];
    // 1. Inisialisasi cURL.
    CURL *curl = curl_easy_init();
    if (!curl) {
        fprintf(stderr, "curl_easy_init() gagal\n");
        goto cleanup;
    }
    // 2. Hitung signature dan dapatkan string kueri kanonis.
    char *signed_query_params = get_authorization(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, authorization_header, hashed_payload, x_acs_date, uuid);
    // 3. Bangun URL permintaan. 
    char url[BUFFER_SIZE];
    if (signed_query_params && strlen(signed_query_params) > 0) {
        snprintf(url, sizeof(url), "https://%s%s?%s", host, canonical_uri, signed_query_params);
    } else {
        snprintf(url, sizeof(url), "https://%s%s", host, canonical_uri);
    }
    printf("Request URL: %s\n", url);
    // Bebaskan memori yang dialokasikan untuk string kueri.
    if (signed_query_params) {
        free(signed_query_params);
    }

    // 4. Tambahkan header permintaan.
    struct curl_slist *headers = NULL;
    char header_value[BUFFER_SIZE];
    snprintf(header_value, sizeof(header_value), "Content-Type: %s", content_type);
    headers = curl_slist_append(headers, header_value);
    snprintf(header_value, sizeof(header_value), "Authorization: %s", authorization_header);
    headers = curl_slist_append(headers, header_value);
    snprintf(header_value, sizeof(header_value), "host: %s", host);
    headers = curl_slist_append(headers, header_value);
    snprintf(header_value, sizeof(header_value), "x-acs-action: %s", x_acs_action);
    headers = curl_slist_append(headers, header_value);
    snprintf(header_value, sizeof(header_value), "x-acs-content-sha256: %s", hashed_payload);
    headers = curl_slist_append(headers, header_value);
    snprintf(header_value, sizeof(header_value), "x-acs-date: %s", x_acs_date);
    headers = curl_slist_append(headers, header_value);
    snprintf(header_value, sizeof(header_value), "x-acs-signature-nonce: %s", uuid);
    headers = curl_slist_append(headers, header_value);
    snprintf(header_value, sizeof(header_value), "x-acs-version: %s", x_acs_version);
    headers = curl_slist_append(headers, header_value);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, http_method);
    curl_easy_setopt(curl, CURLOPT_URL, url);
    // Pengaturan cURL lainnya: nonaktifkan verifikasi SSL dan tambahkan informasi debugging.
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
    // 5. Tambahkan body permintaan.
    if (body) {
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body_length); 
        if (strcmp(content_type, "application/octet-stream") == 0) {
            curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
        } else if (strcmp(content_type, "application/x-www-form-urlencoded") == 0) {
            curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
        } else if (strcmp(content_type, "application/json; charset=utf-8") == 0) {
            curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
        }
    }
    printf("RequestBody:%s\n",body);
    // 6. Kirim permintaan.
    CURLcode res = curl_easy_perform(curl);
    if (res != CURLE_OK) {
        fprintf(stderr, "curl_easy_perform() gagal: %s\n", curl_easy_strerror(res));
        goto cleanup;
    }
cleanup:
    if (headers) curl_slist_free_all(headers);
    if (curl) curl_easy_cleanup(curl);
}
/**
*
     * Ini adalah contoh perhitungan signature. Anda harus mengganti parameter contoh dalam fungsi main dengan nilai aktual Anda.
     * <p>
     * Rujuk metadata API untuk metode permintaan (methods), nama parameter (name), tipe parameter (type), dan lokasi parameter (in).
     * 1. Jika parameter permintaan ditentukan sebagai "in":"query" dalam metadata API, lewatkan melalui string `query_params`. Catatan: Untuk API RPC, tipe parameter ini juga dapat dilewatkan dalam body permintaan dengan `Content-Type` `application/x-www-form-urlencoded`. Lihat contoh "formData" untuk detailnya.
     * 2. Jika parameter permintaan ditentukan sebagai "in": "body", lewatkan dalam body permintaan dengan tipe MIME `application/octet-stream` atau `application/json`. Untuk API RPC, `application/json` tidak disarankan. Gunakan metode yang ditunjukkan dalam contoh "formData" sebagai gantinya.
     * 3. Jika parameter permintaan ditentukan sebagai "in": "formData", lewatkan dalam body permintaan dengan tipe MIME `application/x-www-form-urlencoded`.
*/
int main() {
    // Atur encoding output konsol ke UTF-8.
    SetConsoleOutputCP(CP_UTF8);
    srand((unsigned int)time(NULL));

    /**
      * Contoh permintaan API RPC: "in":"query" dengan parameter kueri kompleks.
    */   
    const char *http_method = "POST";
    const char *canonical_uri = "/";
    const char *host = "tds.cn-shanghai.aliyuncs.com";
    const char *x_acs_action = "AddAssetSelectionCriteria";
    const char *x_acs_version = "2018-12-03";

    // Definisikan string parameter SelectionKey.
    const char *selection_key = "85a561b7-27d5-47ad-a0ec-XXXXXXXX";
    // Definisikan parameter TargetOperationList, daftar objek target yang dapat diperluas.
    struct {
        const char *operation;
        const char *target;
    } targetOperation_list[] = {
        {"add", "i-2ze1j7ocdg9XXXXXXXX"},
        // Anda dapat menambahkan entri lain.
        // {"add", "i-abc123xyzXXXXX"},
    };

    int count = sizeof(targetOperation_list) / sizeof(targetOperation_list[0]);
    KeyValuePair pairs[100]; // Simpan kunci dan nilai asli.
    int pair_count = 0;

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

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

      strncpy(pairs[pair_count].key, target_key, sizeof(pairs[pair_count].key));
      strncpy(pairs[pair_count].value, targetOperation_list[i].target, sizeof(pairs[pair_count].value));
      pair_count++;
}
    // Tambahkan parameter SelectionKey.
    snprintf(pairs[pair_count].key, sizeof(pairs[pair_count].key), "SelectionKey");
    snprintf(pairs[pair_count].value, sizeof(pairs[pair_count].value), "%s", selection_key);
    pair_count++;

    // Pengurutan dan encoding dilakukan dalam fungsi get_authorization().
    qsort(pairs, pair_count, sizeof(KeyValuePair), compare_pairs);

    // Bangun string kueri asli (tidak di-encode).
    char query_params[BUFFER_SIZE] = {0};
    for (int i = 0; i < pair_count; ++i) {
      if (i == 0) {
        snprintf(query_params, sizeof(query_params), "%s=%s", pairs[i].key, pairs[i].value);
     } else {
        char temp[512];
        snprintf(temp, sizeof(temp), "&%s=%s", pairs[i].key, pairs[i].value);
        strncat(query_params, temp, sizeof(query_params) - strlen(query_params) - 1);
    }
}
    const char *body = ""; // Body permintaan kosong.
    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));

    /**
      * Contoh permintaan API RPC: parameter permintaan "in":"query".
    */
    // Definisikan parameter permintaan 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";
    // // Definisikan parameter InstanceId, array opsional.
    // const char *instance_ids[] = {
    //     "i-bp11ht4hXXXXXXXX",
    //     "i-bp16maz3XXXXXXXX"
    // };
    // // Gabungkan array InstanceId.
    // char InstanceId[BUFFER_SIZE];
    // snprintf(InstanceId, sizeof(InstanceId),
    //          "InstanceId.1=%s&InstanceId.2=%s",
    //         instance_ids[0],
    //         instance_ids[1]);
    // // Atur parameter kueri. Parameter RegionId wajib.
    // char query_params[BUFFER_SIZE];
    // snprintf(query_params, sizeof(query_params),
    //          "%s&RegionId=cn-hangzhou", InstanceId);
    // const char *body = "";
    // const char *content_type = "application/json; charset=utf-8";
    // call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));

      /**
        * Contoh permintaan API RPC: parameter permintaan "in":"body" (skenario unggah file).
      */
    // Deklarasikan pointer untuk menyimpan konten file.
    // char *body = NULL;
    // size_t body_length = read_file("<YOUR_FILE_PATH>", &body);
    // if (body_length > 0) {
    //   const char *http_method = "POST";
    //   const char *canonical_uri = "/";
    //   const char *host = "ocr-api.cn-hangzhou.aliyuncs.com";
    //   const char *x_acs_action = "RecognizeGeneral";
    //   const char *x_acs_version = "2021-07-07";
    //   const char *query_params = "";
    //   const char *content_type = "application/octet-stream";
    //   call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, body_length);
    //   free(body);
    // } else {
    //   fprintf(stderr, "Kesalahan membaca file\n");
    // }

      /**
       * Contoh permintaan API RPC: parameter permintaan "in": "formData" atau "in":"body" (skenario bukan unggah file).
       */
    // const char *http_method = "POST";
    // const char *canonical_uri = "/";
    // const char *host = "mt.aliyuncs.com";
    // const char *x_acs_action = "TranslateGeneral";
    // const char *x_acs_version = "2018-10-12";
    // char query_params[BUFFER_SIZE];
    // snprintf(query_params, sizeof(query_params), "Context=%s", "Morning");
    // const char *format_type = "text";
    // const char *source_language = "zh";
    // const char *target_language = "en";
    // const char *source_text = "Hello";
    // const char *scene = "general";
    // char body[BUFFER_SIZE];
    // snprintf(body, sizeof(body),
    // "FormatType=%s&SourceLanguage=%s&TargetLanguage=%s&SourceText=%s&Scene=%s",
    // percentEncode(format_type), percentEncode(source_language), percentEncode(target_language),
    // percentEncode(source_text), percentEncode(scene));
    // const char *content_type = "application/x-www-form-urlencoded";
    // printf("formdate_body: %s\n", body);
    // call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));

   /**
    * Contoh permintaan API RPC: parameter permintaan "in": "formData"
    */
//    const char *http_method = "POST";
//    const char *canonical_uri = "/";
//    const char *host = "sasti.aliyuncs.com";
//    const char *x_acs_action = "AskTextToTextMsg";
//    const char *x_acs_version = "2020-05-12";
//    // kueri
//    const char *query_params = "";
//    // body
//    const char *Memory = "false";
//    const char *Stream = "true";
//    const char *ProductCode = "sddp_pre";
//    const char *Feature = "{}";
//    const char *Model = "yunsec-llm-latest";
//    const char *Type = "Chat";
//    const char *TopP = "0.9";
//    const char *Temperature = "0.01";
//    const char *Prompt = "Who are you";
//    const char *Application = "sddp_pre";
//    char body[BUFFER_SIZE];
//    snprintf(body, sizeof(body),
//            "Memory=%s&Stream=%s&ProductCode=%s&Feature=%s&Model=%s&Type=%s&TopP=%s&Temperature=%s&Prompt=%s&Application=%s",
//            Memory, Stream, ProductCode, Feature, Model, Type, TopP, Temperature, Prompt, Application);
//    const char *content_type = "application/x-www-form-urlencoded";
//    printf("formdate_body: %s\n", body);
//    call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));

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

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

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

    // Variabel untuk menyimpan nilai yang dihasilkan.
    char authorization_header[BUFFER_SIZE];
    char hashed_payload[SHA256_DIGEST_LENGTH * 2 + 1];
    char x_acs_date[64];
    char uuid[37];
    return 0;
}

FAQ

Apa yang harus saya lakukan jika muncul error "Specified signature does not match our calculation" atau "The request signature does not conform to Aliyun standards"?

Verifikasi signature biasanya gagal karena kesalahan dalam proses perhitungan signature.

Penyebab umum:

  • Kesalahan konfigurasi AccessKey.

  • Lokasi parameter salah: Misalnya, melewatkan parameter kueri dalam body permintaan.

  • Parameter dalam string kueri yang dikanonisasi (CanonicalQueryString) tidak diurutkan secara alfabetis.

  • Header yang digunakan untuk perhitungan signature (SignedHeaders) tidak diurutkan secara alfabetis.

  • Karakter spasi tidak di-encode ke %20.

  • Parameter di-encode URL lebih dari sekali. Selama proses perhitungan signature, encoding URL hanya boleh diterapkan sekali pada parameter path dan string kueri kanonis. Misalnya, jika pesan error Anda menunjukkan beberapa karakter %25, ini menunjukkan bahwa karakter % itu sendiri telah di-encode secara salah.

Solusi:

Catatan

Verifikasi hasil perhitungan signature Anda secara lokal menggunakan Nilai parameter tetap.

  1. Bandingkan nilai CanonicalRequest Anda dengan hasil yang diharapkan. Jika berbeda, tinjau Langkah 1: Buat permintaan yang dikanonisasi untuk menemukan perbedaannya.

  2. Jika CanonicalRequest cocok, bandingkan nilai StringToSign. Ketidakcocokan menunjukkan kesalahan algoritma hash.

  3. Jika CanonicalRequest dan StringToSign cocok, masalahnya kemungkinan besar adalah rahasia AccessKey yang salah atau implementasi algoritma enkripsi.

  4. Jika masalah berlanjut, hubungi dukungan teknis.

Bagaimana cara menggunakan Postman untuk debugging?

Untuk debugging panggilan API di Postman:

  1. Hitung signature dan hasilkan nilai header Authorization menggunakan kode atau skrip Anda.

  2. Salin semua header permintaan dari CanonicalHeaders Anda (termasuk header Authorization yang baru saja Anda hasilkan) ke tab Headers di Postman. Contoh:

    Kunci

    Nilai contoh

    host

    dysmsapi.aliyuncs.com

    x-acs-action

    SendSms

    x-acs-content-sha256

    e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

    x-acs-date

    2025-04-16T07:45:55Z

    x-acs-signature-nonce

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

    x-acs-version

    2017-05-25

    Authorization

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

  3. Tambahkan parameter API ke Postman sesuai tipenya.

    Catatan

    Urutan parameter harus identik dengan urutan yang digunakan saat Anda menghitung signature.

    • Untuk parameter kueri, tambahkan di tab Params.

    • Untuk parameter body, tambahkan di tab Body.

Bagaimana cara melewatkan parameter permintaan?

Dalam metadata OpenAPI, field in menentukan lokasi setiap parameter, yang menentukan cara parameter dilewatkan dalam permintaan.

Posisi

Deskripsi

content-type

"in": "query"

Parameter kueri muncul di URL setelah tanda tanya (?). Setiap pasangan kunci-nilai dipisahkan oleh tanda ampersand (&).

Opsional. Jika disediakan, nilainya harus application/json.

"in": "formData"

Parameter formulir dikirim dalam body permintaan. Mereka harus digabungkan menjadi string dengan format key1=value1&key2=value2&key3=value3. Catatan: Untuk tipe kompleks seperti array atau objek, Anda harus "meratakan" nilai menjadi pasangan kunci-nilai yang diindeks. Misalnya, objek seperti {"key":["value1","value2"]} harus dikonversi ke {"key.1":"value1","key.2":"value2"}.

Wajib. Nilainya harus application/x-www-form-urlencoded.

"in": "body"

Parameter body dilewatkan langsung dalam body permintaan.

Wajib. Nilai Content-Type tergantung pada jenis konten yang dikirim. Misalnya:

  • Jika konten permintaan berupa string JSON, atur nilai header Content-Type ke application/json.

  • Jika konten permintaan berupa aliran file biner, atur nilai header Content-Type ke application/octet-stream.

Menurut metadata API, nilai style adalah RPC atau ROA.

RPC atau ROA hanya memengaruhi nilai CanonicalURI. Ketika parameter style diatur ke tipe selain RPC atau ROA, nilai CanonicalURI ditentukan oleh parameter path dalam metadata API. Jika parameter path memiliki nilai, CanonicalURI diatur ke nilai path. Jika parameter path tidak memiliki nilai, CanonicalURI diatur ke garis miring (/). Misalnya, dalam metadata API untuk mengkueri kluster ACK, nilai parameter path adalah /api/v1/clusters, dan nilai CanonicalURI juga /api/v1/clusters.

{
  "code": 0,
  "data": {
    "summary": "Mengkueri detail tentang Container Service for Kubernetes (A...",
    "path": "/api/v1/clusters",
    "methods": [
      "get"
    ],
    "schemes": [
      "http",
      "https"
    ],
    "security": [
      {
        "AK": []
      }
    ]
  }
}

Bagaimana cara melewatkan parameter array atau objek?

Saat melewatkan tipe data kompleks seperti array atau objek, Anda harus mengonversinya menjadi pasangan kunci-nilai yang diindeks.

Contoh 1: {"InstanceId":["i-bp10igfmnyttXXXXXXXX","i-bp1incuofvzxXXXXXXXX","i-bp1incuofvzxXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX"]} harus dikonversi ke:

{
    "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"
}

Contoh 2: {"ImageId":"win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd","RegionId":"cn-shanghai","Tag":[{"tag1":"value1","tag2":"value2"}]} harus dikonversi ke:

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

Bagaimana cara mendapatkan nilai x-acs-version?

  1. Buka Portal Pengembang OpenAPI Alibaba Cloud. Pilih produk cloud untuk API yang ingin Anda panggil. Contoh ini menggunakan ECS. Di bilah navigasi atas portal, klik Pilih Produk Cloud untuk membuka halaman utama produk dan melihat versi API yang direkomendasikan.

  2. Lihat versi API yang direkomendasikan di halaman utama produk. Misalnya, versi API yang direkomendasikan untuk ECS adalah 2014-05-26.

    Di bagian bawah halaman, ada dua tab: Daftar Wilayah Layanan dan Ikhtisar API. Anda dapat menggunakan daftar drop-down versi API untuk beralih ke versi tertentu dan melihat detailnya.

Bisakah saya beralih antara metode GET dan POST untuk panggilan API?

  • Dalam kebanyakan kasus, Anda dapat menggunakan metode POST atau GET untuk memanggil operasi API dalam gaya RPC.

  • Namun, Anda hanya dapat menggunakan satu metode untuk memanggil operasi API dalam gaya ROA.

Periksa metode yang didukung dalam metadata API.

Apa yang harus saya lakukan jika muncul error "You are not authorized to do this operation."?

Penyebab: Pasangan AccessKey Pengguna RAM tidak memiliki izin yang diperlukan.

Solusi: Apa yang harus saya lakukan jika muncul pesan error "kode 403. You are not authorized to do this operation. Action: xxxx"?

Bagaimana cara mendapatkan pasangan AccessKey?

Pasangan AccessKey adalah kredensial akses permanen yang terdiri dari ID AccessKey dan rahasia AccessKey. Alibaba Cloud menggunakan pasangan AccessKey untuk mengotentikasi pemanggil API. Buat AccessKey untuk Pengguna RAM.

Hubungi kami

Untuk pertanyaan terkait signature, bergabunglah dengan grup DingTalk (ID: 147535001692).

Catatan

Jangan bergabung dengan grup ini untuk pertanyaan lain.