All Products
Search
Document Center

Alibaba Cloud SDK:Mekanisme badan permintaan dan signature bergaya ROA V2

Last Updated:Jun 03, 2026

Gunakan metode penandatanganan mandiri bergaya ROA untuk mengirim permintaan HTTP ke Alibaba Cloud OpenAPI.

Penting

Metode akses ini tidak lagi direkomendasikan. Gunakan mekanisme badan permintaan dan signature V3 sebagai gantinya.

Struktur permintaan HTTP

Permintaan OpenAPI lengkap mencakup bagian-bagian berikut.

Name

Required

Description

Example

Protocol

Yes

Protokol permintaan. Periksa metadata OpenAPI untuk protokol yang didukung. Jika HTTP dan HTTPS keduanya didukung, gunakan HTTPS.

https://

Endpoint

Yes

Titik akhir layanan. Temukan titik akhir spesifik produk dalam dokumentasi titik akhir yang sesuai.

bailian.cn-beijing.aliyuncs.com

resource_URI_parameters

Yes

URL API, termasuk path dan parameter kueri.

/{WorkspaceId}/datacenter/category

RequestHeader

Yes

Header permintaan, termasuk versi API, Host, dan Authorization. Lihat bagian RequestHeader di bawah ini.

Accept:application/json

Authorization:acs YourAccessKeyId:s8ZMF/eAIvvPJwehLLha0bVNFJ0=

Content-MD5:LP54yxk8n7KqF1PPgbJizw==

Content-Type:application/json

Date:Wed, 16 Apr 2025 03:44:46 GMT

Host:bailian.cn-beijing.aliyuncs.com

x-acs-signature-method:HMAC-SHA1

x-acs-signature-nonce:ef34aae7-7bd2-413d-a541-680cd2c48538

x-acs-signature-version:1.0

x-acs-version:2023-12-29

RequestBody

Yes

Badan permintaan yang berisi parameter bisnis. Coba permintaan Anda di Alibaba Cloud OpenAPI Developer Portal.

{"CategoryName":"test","CategoryType":"UNSTRUCTURED"}

HTTPMethod

Yes

Metode HTTP: PUT, POST, GET, atau DELETE. Periksa dokumentasi API untuk metode yang diperlukan.

POST

RequestHeader

RequestHeader mencakup bidang-bidang berikut.

Name

Type

Required

Description

Example

Accept

String

No

Format respons. ROA hanya mendukung application/json.

application/json

Content-MD5

String

No

Hash MD5 128-bit dari RequestBody, dikonversi menjadi string terenkripsi Base64.

LP54yxk8n7KqF1PPgbJizw==

Date

String

Yes

Timestamp saat ini dalam format GMT HTTP 1.1. Berlaku selama 15 menit. Contoh: Tue 9 Apr 2019 07:35:29 GMT.

Wed, 16 Apr 2025 03:44:46 GMT

Host

String

Yes

Titik akhir layanan. Lihat bidang endpoint di struktur permintaan HTTP.

bailian.cn-beijing.aliyuncs.com

x-acs-signature-method

String

Required for non-anonymous requests

Algoritma penandatanganan. Nilai tetap: HMAC-SHA1.

HMAC-SHA1

x-acs-signature-nonce

String

Yes

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

ef34aae7-7bd2-413d-a541-680cd2c48538

x-acs-signature-version

String

No

Versi signature. Nilai tetap: 1.0.

1.0

x-acs-version

String

Yes

Versi API. Temukan versi tersebut di Alibaba Cloud OpenAPI Developer Portal atau metadata OpenAPI.

2023-12-29

Authorization

String

Required for non-anonymous requests

String otentikasi dalam format AccessKeyId:Signature. Lihat ID AccessKey Anda di Konsol RAM. Untuk membuatnya, ikuti Buat AccessKey.

Signature adalah signature permintaan. Untuk nilainya, lihat Mekanisme signature.

acs YourAccessKeyId:D9uFJAJgLL+dryjBfQK+YeqGtoY=

Mekanisme signature

Semua permintaan HTTP dan HTTPS harus menyertakan signature untuk otentikasi. Alibaba Cloud API Gateway menghitung ulang signature dari parameter dan header permintaan, lalu membandingkannya dengan signature dalam permintaan untuk memverifikasi identitas pemohon dan integritas data. Perhitungan signature mengikuti langkah-langkah berikut:

Catatan

Permintaan dan respons dienkoding dalam set karakter UTF-8.

Langkah 1: Buat header terkanonisasi

Header terkanonisasi (CanonicalizedHeaders) adalah header HTTP non-standar yang diawali dengan x-acs-. Buat seperti berikut:

  1. Ubah nama header di RequestHeader yang dimulai dengan x-acs- menjadi huruf kecil dan urutkan secara leksikografis menaik.

  2. Hapus spasi awal dan akhir dari nama dan nilai header.

  3. Tambahkan \n setelah setiap header (termasuk header terakhir), lalu gabungkan semua header untuk membentuk CanonicalizedHeaders.

Contoh:

x-acs-signature-method:HMAC-SHA1
x-acs-signature-nonce:ef34aae7-7bd2-413d-a541-680cd2c48538
x-acs-signature-version:1.0
x-acs-version:2023-12-29

Langkah 2: Buat resource terkanonisasi

Resource terkanonisasi (CanonicalizedResource) menggambarkan resource target. Buat seperti berikut:

  1. Urutkan parameter string kueri berdasarkan nama secara leksikografis dan gabungkan dengan &.

  2. Tambahkan string kueri yang telah diurutkan ke path resource menggunakan ? sebagai pemisah. Path resource adalah segmen URL antara host dan string kueri, dimulai dengan /. Jika tidak ada string kueri, gunakan hanya path resource.

Contoh:

/llm-p2e4XXXXXXXXsvtn/datacenter/category

Langkah 3: Buat string yang akan ditandatangani

Buat string yang akan ditandatangani (stringToSign) menggunakan pseudokode berikut:

String stringToSign = 
    HTTPMethod + "\n" +
    Accept + "\n" +
    ContentMD5 + "\n" +
    ContentType + "\n" +
    Date + "\n" +
    CanonicalizedHeaders +
    CanonicalizedResource

Parameter

Description

HTTPMethod

Nama metode HTTP dalam huruf kapital, seperti POST atau GET.

Accept

Nilai header Accept. Gunakan string kosong jika tidak ada.

ContentMD5

Nilai header Content-MD5. Gunakan string kosong jika tidak ada.

ContentType

Nilai header Content-Type. Gunakan string kosong jika tidak ada.

Catatan

Jenis MIME dari RequestBody.

Date

Nilai header Date.

CanonicalizedHeaders

Header terkanonisasi yang diperoleh pada Langkah 1: Buat header terkanonisasi.

CanonicalizedResource

Resource terkanonisasi yang diperoleh pada Langkah 2: Buat resource terkanonisasi.

Contoh:

POST
application/json
LP54yxk8n7KqF1PPgbJizw==
application/json
Wed, 16 Apr 2025 03:44:46 GMT
x-acs-signature-method:HMAC-SHA1
x-acs-signature-nonce:ef34aae7-7bd2-413d-a541-680cd2c48538
x-acs-signature-version:1.0
x-acs-version:2023-12-29
/llm-p2e4XXXXXXXXsvtn/datacenter/category

Langkah 4: Hitung signature

Seperti yang didefinisikan dalam RFC2104, tandatangani string dari langkah sebelumnya menggunakan HMAC-SHA1, lalu enkode hasilnya dengan Base64.

String signature = Base64(HMAC_SHA1(SigningKey, stringToSign))
Penting

SigningKey adalah rahasia AccessKey Anda. Buat AccessKey.

Contoh:

s8ZMF/eAIvvPJwehLLha0bVNFJ0=

Langkah 5: Buat header Authorization

Gabungkan string-string berikut dan tetapkan hasilnya sebagai nilai header Authorization.

String Authorization = "acs " + AccessKeyId + ":" + signature

Nilai contoh:

acs YourAccessKeyId:s8ZMF/eAIvvPJwehLLha0bVNFJ0=

Langkah 6: Tambahkan signature ke permintaan dan kirim permintaan

POST https://bailian.cn-beijing.aliyuncs.com/llm-p2e4XXXXXXXXsvtn/datacenter/category HTTP/1.1
 
Accept:application/json
Authorization:acs YourAccessKeyId:r8Y9ZqVhTrYGl4nieqk7CW0Pwow=
Content-MD5:LP54yxk8n7KqF1PPgbJizw==
Content-Type:application/json
Date:Wed, 16 Apr 2025 06:47:10 GMT
Host:bailian.cn-beijing.aliyuncs.com
x-acs-signature-method:HMAC-SHA1
x-acs-signature-nonce:e3d8efa7-b1d8-42f3-9733-4fe2691e15dc
x-acs-signature-version:1.0
x-acs-version:2023-12-29

{"CategoryName":"test","CategoryType":"UNSTRUCTURED"}

Kode contoh signature

Contoh Java

Catatan

Kode contoh dijalankan pada JDK 1.8. Sesuaikan sesuai kebutuhan lingkungan Anda.

Tambahkan dependensi Maven berikut ke file pom.xml Anda.

<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.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;


public class SignatureDemo {
    public static class SignatureRequest {
        private final String httpMethod;
        private final String host;
        private final String version;
        private final String canonicalUri;

        public TreeMap<String, String> headers = new TreeMap<>();
        public TreeMap<String, Object> queryParams = new TreeMap<>();
        public byte[] bodyByteArray;

        public SignatureRequest(String httpMethod, String host, String version, String canonicalUri) {
            this.httpMethod = httpMethod;
            this.host = host;
            this.version = version;
            this.canonicalUri = canonicalUri;
            initHeaders();
        }

        private void initHeaders() {
            headers.put("Host", host);
            headers.put("x-acs-version", version);
            headers.put("x-acs-signature-version", "1.0");
            headers.put("Accept", "application/json");
            headers.put("x-acs-signature-nonce", java.util.UUID.randomUUID().toString());
            headers.put("Date", getGmtDate());
            headers.put("x-acs-signature-method", "HMAC-SHA1");
        }

        private String getGmtDate() {
            SimpleDateFormat gmtFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.ENGLISH);
            gmtFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
            return gmtFormat.format(new Date());
        }

        public void setBody(Map<String, String> body, ContentType contentType) {
            Gson gson = (new GsonBuilder()).disableHtmlEscaping().create();
            this.bodyByteArray = gson.toJson(body).getBytes(StandardCharsets.UTF_8);
            headers.put("Content-MD5", md5Sum(this.bodyByteArray));
            headers.put("Content-Type", contentType.getMimeType());
        }
    }

    private static final String ACCESS_KEY_ID = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
    private static final String ACCESS_KEY_SECRET = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
    private static final String ALGORITHM_NAME = "HmacSHA1";

    public static void main(String[] args) {
        // Contoh 1: Permintaan POST
        String method = "POST";
        String host = "bailian.cn-beijing.aliyuncs.com";
        String version = "2023-12-29";
        String canonicalUri = "/llm-p2e4XXXXXXXXsvtn/datacenter/category";
        SignatureRequest signatureRequest = new SignatureRequest(method, host, version, canonicalUri);
        Map<String, String> body = new HashMap<>();
        body.put("CategoryName", "test");
        body.put("CategoryType", "UNSTRUCTURED");
        System.out.println(new Gson().toJson(body));
        signatureRequest.setBody(body, ContentType.APPLICATION_JSON);

        /*// Contoh 2: Permintaan GET
        String method = "GET";
        String host = "bailian.cn-beijing.aliyuncs.com";
        String version = "2023-12-29";
        String canonicalUri = "/llm-p2e4XXXXXXXXsvtn/datacenter/files";
        SignatureRequest signatureRequest = new SignatureRequest(method, host, version, canonicalUri);
        signatureRequest.queryParams.put("CategoryId", "cate_a946*********************_10045991");*/

        /*// Contoh 3: Permintaan DELETE
        String method = "DELETE";
        String host = "bailian.cn-beijing.aliyuncs.com";
        String version = "2023-12-29";
        String canonicalUri = "/llm-p2e4XXXXXXXXsvtn/datacenter/category/cate_a946*********************_10045991";
        SignatureRequest signatureRequest = new SignatureRequest(method, host,  version, canonicalUri);*/

        // Hasilkan Authorization
        generateSignature(signatureRequest);
        // Kirim permintaan menggunakan HTTPClient
        callApi(signatureRequest);
    }

    private static void generateSignature(SignatureRequest signatureRequest) {
        try {
            // Langkah 1: Buat header terkanonisasi
            String canonicalHeaders = buildCanonicalHeaders(signatureRequest);
            // Langkah 2: Buat resource terkanonisasi
            String canonicalQueryString = buildQueryString(signatureRequest);
            // Langkah 3: Buat string yang akan ditandatangani
            String stringToSign = buildStringToSign(signatureRequest, canonicalQueryString, canonicalHeaders);
            // Langkah 4: Buat string signature
            String signature = signString(stringToSign);
            // Langkah 5: Buat header Authorization
            String authorization = "acs " + ACCESS_KEY_ID + ":" + signature;
            signatureRequest.headers.put("Authorization", authorization);
        } catch (Exception ex) {
            throw new IllegalArgumentException(ex.toString());
        }
    }

    private static void callApi(SignatureRequest signatureRequest) {
        try {
            // Kirim permintaan menggunakan HttpClient
            String url = "https://" + signatureRequest.host + signatureRequest.canonicalUri;
            URIBuilder uriBuilder = new URIBuilder(url);
            // Tambahkan parameter permintaan
            signatureRequest.queryParams.forEach((key, value) -> uriBuilder.addParameter(key, String.valueOf(value)));
            HttpUriRequest httpRequest;
            switch (signatureRequest.httpMethod) {
                case "GET":
                    httpRequest = new HttpGet(uriBuilder.build());
                    break;
                case "POST":
                    HttpPost httpPost = new HttpPost(uriBuilder.build());
                    if (signatureRequest.bodyByteArray != null) {
                        httpPost.setEntity(new ByteArrayEntity(signatureRequest.bodyByteArray, ContentType.create(signatureRequest.headers.get("Content-Type"))));
                    }
                    httpRequest = httpPost;
                    break;
                case "PUT":
                    HttpPut httpPut = new HttpPut(uriBuilder.build());
                    if (signatureRequest.bodyByteArray != null) {
                        httpPut.setEntity(new ByteArrayEntity(signatureRequest.bodyByteArray, ContentType.create(signatureRequest.headers.get("Content-Type"))));
                    }
                    httpRequest = httpPut;
                    break;
                case "DELETE":
                    httpRequest = new HttpDelete(uriBuilder.build());
                    break;
                default:
                    System.out.println("Metode HTTP tidak didukung: " + signatureRequest.httpMethod);
                    throw new IllegalArgumentException("Metode HTTP tidak didukung");
            }
            // Tambahkan header permintaan HTTP
            signatureRequest.headers.forEach(httpRequest::addHeader);
            // Kirim permintaan
            try (CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(httpRequest)) {
                String result = EntityUtils.toString(response.getEntity(), "UTF-8");
                System.out.println(result);
            } catch (IOException e) {
                // Tangani pengecualian
                System.out.println("Gagal mengirim permintaan");
                throw new IllegalArgumentException(e.toString());
            }
        } catch (URISyntaxException e) {
            // Tangani pengecualian
            System.out.println("Sintaks URI tidak valid");
            throw new IllegalArgumentException(e.toString());
        }
    }

    private static String buildCanonicalHeaders(SignatureRequest signatureRequest) {
        StringBuilder canonicalHeaders = new StringBuilder();
        signatureRequest.headers.entrySet().stream()
                .filter(entry -> entry.getKey().startsWith("x-acs-"))
                .forEach(entry -> canonicalHeaders.append(entry.getKey().toLowerCase().trim()).append(":").append(entry.getValue().trim()).append("\n"));
        return canonicalHeaders.toString();
    }

    private static String buildQueryString(SignatureRequest signatureRequest) {
        StringBuilder queryBuilder = new StringBuilder(signatureRequest.canonicalUri);
        if (!signatureRequest.queryParams.isEmpty()) {
            queryBuilder.append("?");
        }

        for (Map.Entry<String, Object> entry : signatureRequest.queryParams.entrySet()) {
            queryBuilder.append(entry.getKey());
            String value = (String) entry.getValue();
            if (value != null && !value.isEmpty()) {
                queryBuilder.append("=").append(value).append("&");
            } else {
                queryBuilder.append("&");
            }
        }
        String queryString = queryBuilder.toString();
        if (queryString.endsWith("&")) {
            queryString = queryString.substring(0, queryString.length() - 1);
        }
        return queryString;
    }

    private static String buildStringToSign(SignatureRequest signatureRequest, String canonicalQueryString, String canonicalHeaders) {
        StringBuilder sb = new StringBuilder();
        sb.append(signatureRequest.httpMethod).append("\n");
        appendIfPresent(sb, signatureRequest.headers.get("Accept"));
        appendIfPresent(sb, signatureRequest.headers.get("Content-MD5"));
        appendIfPresent(sb, signatureRequest.headers.get("Content-Type"));
        appendIfPresent(sb, signatureRequest.headers.get("Date"));
        sb.append(canonicalHeaders);
        sb.append(canonicalQueryString);
        return sb.toString();
    }

    private static void appendIfPresent(StringBuilder sb, String value) {
        if (value != null) {
            sb.append(value).append("\n");
        } else {
            sb.append("\n");
        }
    }

    private static String signString(String stringToSign) {
        try {
            Mac mac = Mac.getInstance(ALGORITHM_NAME);
            mac.init(new SecretKeySpec(ACCESS_KEY_SECRET.getBytes(StandardCharsets.UTF_8), ALGORITHM_NAME));
            byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
            return DatatypeConverter.printBase64Binary(signData);
        } catch (NoSuchAlgorithmException | InvalidKeyException ex) {
            throw new IllegalArgumentException(ex.toString());
        }
    }

    public static String md5Sum(byte[] buff) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] md5Bytes = md.digest(buff);
            return DatatypeConverter.printBase64Binary(md5Bytes);
        } catch (NoSuchAlgorithmException ex) {
            throw new IllegalArgumentException(ex.toString());
        }
    }
}

Referensi

Untuk memahami perbedaan antara dua gaya API tersebut, lihat Membedakan gaya ROA dan RPC.