全部产品
Search
文档中心

阿里云SDK:V2版本ROA风格请求体&签名机制

更新时间:May 07, 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网关时,网关将根据请求中的参数及请求头重新计算签名,并与请求中包含的签名信息进行对比,以验证请求者的身份,并确保传输数据的完整性与安全性。以下是签名计算的步骤:

说明

请求及返回结果都使用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请求头的值,当请求头不存在时,使用空字符串代替。

ContentMD5

Content-MD5请求头的值,当请求头不存在时,使用空字符串代替。

ContentType

Content-Type请求头的值,当请求头不存在时,使用空字符串代替。

说明

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风格