全部產品
Search
文件中心

Alibaba Cloud SDK:V2版本ROA風格請求體&簽名機制

更新時間:May 08, 2025

本文為您介紹如何通過ROA風格的自簽名方式發起HTTP請求,調用阿里雲OpenAPI。

重要

不再推薦使用該訪問方式,請移步參考V3版本請求體&簽名機制

HTTP 要求結構

一個完整的阿里雲 OpenAPI 請求,包含以下部分。

名稱

是否必選

描述

樣本值

協議

請求協議,您可以通過調用OpenAPI中繼資料查看API支援的請求協議。若API同時支援HTTPHTTPS時,為了確保更高的安全性,建議您使用HTTPS協議發送請求。

https://

服務地址

即 Endpoint。您可以查閱不同雲產品的服務接入地址文檔,查閱不同服務地區下的服務地址。

bailian.cn-beijing.aliyuncs.com

resource_URI_parameters

介面URL,包括介面路徑和位置在 path、 query的介面請求參數。

/{WorkspaceId}/datacenter/category

RequestHeader

要求標頭資訊,通常包含API的版本、Host、Authorization等資訊。後文將詳細說明,請參見RequestHeader(要求標頭

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

定義在 body 中的業務請求參數,建議您在阿里雲 OpenAPI 開發人員門戶進行試用。

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

HTTPMethod

要求方法,要求方法包括PUT、POST、GET、DELETE。介面的要求方法可通過對應的API文檔擷取。

POST

RequestHeader(要求標頭)

一個完整的阿里雲 OpenAPI 請求,包含以下部分。

名稱

類型

是否必選

描述

樣本值

Accept

String

指定介面返回資料的格式,該參數未傳時,以XML形式返回資料。ROA 只支援固定值 application/json

application/json

Content-MD5

String

RequestBody的128-bit MD5散列值轉換成BASE64編碼的結果。

LP54yxk8n7KqF1PPgbJizw==

Date

String

目前時間戳,有效期間為15分鐘,即產生時間戳記後需要在15分鐘內發起請求。HTTP 1.1協議中規定的 GMT 時間,例如:Tue 9 Apr 2019 07:35:29 GMT。

Wed, 16 Apr 2025 03:44:46 GMT

Host

String

即服務地址,參見HTTP 要求結構中的服務地址。

bailian.cn-beijing.aliyuncs.com

x-acs-signature-method

String

非匿名請求必須

簽名方式。目前為固定值 HMAC-SHA1

HMAC-SHA1

x-acs-signature-nonce

String

簽名唯一隨機數。用於防止網路重放攻擊,建議您每次請求都使用不同的隨機數。

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

x-acs-signature-version

String

簽名版本。目前為固定值 1.0

1.0

x-acs-version

String

API 版本。您可以訪問阿里雲 OpenAPI 開發人員門戶或者通過OpenAPI中繼資料擷取OpenAPI 對應的 API 版本。

2023-12-29

Authorization

String

非匿名請求必須

用於驗證請求合法性的認證資訊,格式為AccessKeyId:Signature。其中AccessKeyId 為使用者的存取金鑰ID。您可以在RAM 控制台查看您的 AccessKeyId。如需建立 AccessKey,請參見建立AccessKey

Signature為請求籤名,取值參見簽名機制

acs YourAccessKeyId:D9uFJAJgLL+dryjBfQK+YeqGtoY=

簽名機制

為確保API的安全調用,所有HTTP或HTTPS請求均需在請求中包含簽名資訊。當請求到達阿里雲API Gateway時,網關將根據請求中的參數及要求標頭重新計算簽名,並與請求中包含的簽名資訊進行對比,以驗證要求者的身份,並確保傳輸資料的完整性與安全性。以下是簽名計算的步驟:

說明

請求及返回結果都使用UTF-8字元集進行編碼。

步驟一:構造正常化要求標頭

阿里雲正常化要求標頭(CanonicalizedHeaders)是非標準HTTP頭部資訊。它是指請求中出現的以x-acs-為首碼的參數資訊,構造方法如下:

  1. RequestHeader(要求標頭)中以x-acs-為首碼的要求標頭名稱轉換成小寫字母,並按照字典順序進行升序排列。

  2. 對要求標頭的名稱和值分別進行去除首尾空格操作。

  3. 在每一個要求標頭後添加一個\n分隔字元(包括最後一個要求標頭),然後將所有要求標頭拼接在一起即獲得CanonicalizedHeaders。

樣本值:

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

步驟二:構造正常化資源

正常化資源(CanonicalizedResource) 表示您想要訪問資源的規範描述,具體構造方式如下:

  1. 將請求的查詢字串(queryString)中的參數按照參數名稱的字典序重新排序,並以&分隔字元串連產生已排序查詢字串。

  2. 將請求資源路徑(指URL中host與查詢字串之間的部分,包含host之後的/但不包含查詢字串前的?)與已排序查詢字串以?拼接,得到正常化資源。當查詢字串不存在時,直接用請求資源路徑作為正常化資源。

樣本值:

/llm-p2e4XXXXXXXXsvtn/datacenter/category

步驟三:構造待簽名字串

按照以下虛擬碼構造待簽名字串(stringToSign):

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

參數

描述

HTTPMethod

大寫的HTTP方法名,例如POST、GET。

Accept

Accept要求標頭的值,當要求標頭不存在時,使用Null 字元串代替。

ContentMD5

Content-MD5要求標頭的值,當要求標頭不存在時,使用Null 字元串代替。

ContentType

Content-Type要求標頭的值,當要求標頭不存在時,使用Null 字元串代替。

說明

RequestBody對應的MIME類型。

Date

Date要求標頭的值。

CanonicalizedHeaders

步驟一:構造正常化要求標頭中獲得的正常化要求標頭。

CanonicalizedResource

步驟二:構造正常化資源中獲得的正常化資源。

樣本值:

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

步驟四:計算簽名

根據RFC2104的定義,按照HMAC-SHA1演算法對上一步產生的待簽名字串進行簽名計算,並以Base64編碼規則將計算結果編碼成字串,即得到最終的簽名值(signature)。

String signature = Base64(HMAC_SHA1(SigningKey, stringToSign))
重要

計算簽名時使用的SigningKey值就是您的AccessKey Secret。更多資訊,請參見建立AccessKey

樣本值:

s8ZMF/eAIvvPJwehLLha0bVNFJ0=

步驟五:構造Authorization

計算出簽名結果signature後,按照以下規則拼接字串,並將結果作為Authorization要求標頭的值。

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

樣本值:

acs YourAccessKeyId:s8ZMF/eAIvvPJwehLLha0bVNFJ0=

步驟六:將簽名添加到請求中並發起請求

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

簽名範例程式碼

Java樣本

說明

範例程式碼的運行環境是JDK1.8,您可能需要根據具體情況對代碼進行相應的調整。

運行Java樣本,需要您在pom.xml中添加以下Maven依賴。

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>
<dependency>
     <groupId>com.google.code.gson</groupId>
     <artifactId>gson</artifactId>
     <version>2.9.0</version>
 </dependency>
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.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) {
        // 樣本一: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);

        /*// 樣本二: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");*/

        /*// 樣本三: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);*/

        // 產生Authorization
        generateSignature(signatureRequest);
        // 通過HTTPClient發送請求
        callApi(signatureRequest);
    }

    private static void generateSignature(SignatureRequest signatureRequest) {
        try {
            // 步驟一:構造正常化要求標頭
            String canonicalHeaders = buildCanonicalHeaders(signatureRequest);
            // 步驟二:構造正常化資源
            String canonicalQueryString = buildQueryString(signatureRequest);
            // 步驟三:構造正常化請求字串
            String stringToSign = buildStringToSign(signatureRequest, canonicalQueryString, canonicalHeaders);
            // 步驟四:建構簽章字串
            String signature = signString(stringToSign);
            // 步驟五:構造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 {
            // 通過HttpClient發送請求
            String url = "https://" + signatureRequest.host + signatureRequest.canonicalUri;
            URIBuilder uriBuilder = new URIBuilder(url);
            // 添加請求參數
            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("Unsupported HTTP method: " + signatureRequest.httpMethod);
                    throw new IllegalArgumentException("Unsupported HTTP method");
            }
            // 添加http要求標頭
            signatureRequest.headers.forEach(httpRequest::addHeader);
            // 發送請求
            try (CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(httpRequest)) {
                String result = EntityUtils.toString(response.getEntity(), "UTF-8");
                System.out.println(result);
            } catch (IOException e) {
                // 異常處理
                System.out.println("Failed to send request");
                throw new IllegalArgumentException(e.toString());
            }
        } catch (URISyntaxException e) {
            // 異常處理
            System.out.println("Invalid URI syntax");
            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());
        }
    }
}

相關文檔

您可以通過以下文檔詳細瞭解兩種API風格的區別,具體請參見區分ROA風格和RPC風格