为保证API的安全使用,在调用API时阿里云会对每个API请求通过签名(Signature)进行身份验证。无论使用HTTP还是HTTPS协议提交请求,都需要在请求中包含签名信息。本文介绍计算签名的方法和示例。

步骤一:构造规范化请求字符串

  1. 排序参数。按照参数首字母的字典顺序对参数排序,排序参数包括公共请求参数和接口自定义参数,不包括公共请求参数中的Signature参数。 公共请求参数详情,请参见公共请求参数
    说明 当使用GET方法提交请求时,这些参数就是URL请求中的参数部分,即URL中?之后由&连接的部分。
  2. 编码参数。使用UTF-8字符集按照RFC3986规则编码请求参数和参数取值,编码规则如下:
    • 字符A~Z、a~z、0~9以及字符-_.~不编码。
    • 其它字符编码成%XY的格式,其中XY是字符对应ASCII码的16进制。示例:半角双引号(")对应%22
    • 扩展的UTF-8字符,编码成%XY%ZA…的格式。
    • 空格编码成%20,而不是加号(+)。

      该编码方式与application/x-www-form-urlencodedMIME格式编码算法相似,但又有所不同。

      如果您使用的是Java标准库中的java.net.URLEncoder,可以先用标准库中的percentEncode编码,然后将已编码字符中的加号(+)替换为%20、星号(*)替换为%2A%7E替换为波浪号(~),即可得到上述规则描述的编码字符串。

      private static final String ENCODING = "UTF-8";
      private static String percentEncode(String value) throws UnsupportedEncodingException {
      return value != null ? URLEncoder.encode(value, ENCODING).replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null;
      }
  3. 使用等号(=)连接编码后的请求参数和参数取值。
  4. 使用与号(&)连接编码后的请求参数,注意参数排序与第1步一致。

现在,您得到了规范化请求字符串(CanonicalizedQueryString),其结构遵循请求结构

步骤二:构造签名字符串

  1. 构造待签名字符串StringToSign。您可以同样使用percentEncode处理上一步构造出的规范化请求字符串,规则如下:
    StringToSign=
      HTTPMethod + "&" + //HTTPMethod:发送请求的 HTTP 方法,例如 GET。
      percentEncode("/") + "&" + //percentEncode("/"):字符(/)UTF-8 编码得到的值,即 %2F。
      percentEncode(CanonicalizedQueryString) //您的规范化请求字符串。
  2. 按照RFC2104的定义,计算待签名字符串StringToSign的HMAC-SHA1值。本示例使用的是Java Base64编码方法。
    Signature = Base64( HMAC-SHA1( AccessSecret, UTF-8-Encoding-Of(StringToSign) ) )
    说明 计算签名时,RFC2104规定的Key值是您的AccessKeySecret并加上与号(&),其ASCII值为38。更多详情,请参见创建AccessKey
  3. RFC3986规则编码后的参数Signature添加到规范化请求字符串URL中。

示例一:参数拼接法

本示例以调用DescribeDedicatedHosts查询一台或多台专有宿主机的详细信息为例。假设您获得了AccessKeyID=testid以及AccessKeySecret=testsecret,签名流程如下:
  1. 构造规范化请求字符串。
    http://ecs.aliyuncs.com/?Timestamp=2016-02-23T12%3A46%3A24Z&Format=XML&AccessKeyId=testid&Action=DescribeDedicatedHosts&SignatureMethod=HMAC-SHA1&SignatureNonce=3ee8c1b8-xxxx-xxxx-xxxx-xxxxxxxxxx&Version=2014-05-26&SignatureVersion=1.0
  2. 构造待签名字符串StringToSign
    GET&%2F&AccessKeyId%3Dtestid%26Action%3DDescribeDedicatedHosts%26Format%3DXML%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D3ee8c1b8-xxxx-xxxx-xxxx-xxxxxxxxx%26SignatureVersion%3D1.0%26Timestamp%3D2016-02-23T12%253A46%253A24Z%26Version%3D2014-05-26
  3. 计算签名值。因为AccessKeySecret=testsecret,用于计算的Key为testsecret&,计算得到的签名值为OLeaidS1JvxuMvnyHOwuJ+uX5qY=。本示例使用的是JavaBase64编码方法。
    Signature = Base64( HMAC-SHA1( AccessSecret, UTF-8-Encoding-Of(StringToSign) ) )
  4. 添加RFC3986规则编码后的Signature=OLeaidS1JvxuMvnyHOwuJ%2BuX5qY%3D第1步的URL中。
    http://ecs.aliyuncs.com/?SignatureVersion=1.0&Action=DescribeDedicatedHosts&Format=XML&SignatureNonce=3ee8c1b8-xxxx-xxxx-xxxx-xxxxxxxxx&Version=2014-05-26&AccessKeyId=testid&Signature=OLeaidS1JvxuMvnyHOwuJ%2BuX5qY%3D&SignatureMethod=HMAC-SHA1&Timestamp=2016-02-23T12%253A46%253A24Z

通过第4步得到的URL,您可以使用浏览器、curl或者wget等工具发起HTTP请求调用DescribeDedicatedHosts,查询一台或多台专有宿主机的详细信息。

示例二:编程语言法

本示例以调用DescribeDedicatedHosts为例。假设您获得了AccessKeyID=testid以及AccessKeySecret=testsecret,并且假定所有请求参数放在一个JavaMap<String, String>对象里。

  1. 预定义编码方法。
    private static final String ENCODING = "UTF-8";
    private static String percentEncode(String value) throws UnsupportedEncodingException {
      return value != null ? URLEncoder.encode(value, ENCODING).replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null;
    }
  2. 预定义编码时间格式Timestamp。参数Timestamp必须符合ISO8601规范,并需要使用UTC时间,时区为+0。
    private static final String ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
    private static String formatIso8601Date(Date date) {
      SimpleDateFormat df = new SimpleDateFormat(ISO8601_DATE_FORMAT);
      df.setTimeZone(new SimpleTimeZone(0, "GMT"));
      return df.format(date);
    }
  3. 构造请求字符串。
    final String HTTP_METHOD = "GET";
    Map parameters = new HashMap();
    // 输入请求参数
    parameters.put("Action", "DescribeDedicatedHosts");
    parameters.put("Version", "2014-05-26");
    parameters.put("AccessKeyId", "testid");
    parameters.put("Timestamp", formatIso8601Date(new Date()));
    parameters.put("SignatureMethod", "HMAC-SHA1");
    parameters.put("SignatureVersion", "1.0");
    parameters.put("SignatureNonce", UUID.randomUUID().toString());
    parameters.put("Format", "XML");
    // 排序请求参数
    String[] sortedKeys = parameters.keySet().toArray(new String[]{});
    Arrays.sort(sortedKeys);
    final String SEPARATOR = "&";
    // 构造 stringToSign 字符串
    StringBuilder stringToSign = new StringBuilder();
    stringToSign.append(HTTP_METHOD).append(SEPARATOR);
    stringToSign.append(percentEncode("/")).append(SEPARATOR);
    StringBuilder canonicalizedQueryString = new StringBuilder();
    for(String key : sortedKeys) {
    // 这里注意编码 key 和 value
      canonicalizedQueryString.append("&")
      .append(percentEncode(key)).append("=")
      .append(percentEncode(parameters.get(key)));
    }
    // 这里注意编码 canonicalizedQueryString
    stringToSign.append(percentEncode(
      canonicalizedQueryString.toString().substring(1)));
  4. 签名。因为AccessKeySecret=testsecret,所以用于计算HMAC的Key为testsecret&,计算得到的签名值为OLeaidS1JvxuMvnyHOwuJ%2BuX5qY%3D
    // 以下是一段计算签名的示例代码
    final String ALGORITHM = "HmacSHA1";
    final String ENCODING = "UTF-8";
    key = "testsecret&";
    Mac mac = Mac.getInstance(ALGORITHM);
    mac.init(new SecretKeySpec(key.getBytes(ENCODING), ALGORITHM));
    byte[] signData = mac.doFinal(stringToSign.getBytes(ENCODING));
    String signature = new String(Base64.encodeBase64(signData));
    增加签名参数后,按照RFC3986规则编码后的URL如下:
    http://ecs.aliyuncs.com/?SignatureVersion=1.0&Action=DescribeDedicatedHosts&Format=XML&SignatureNonce=3ee8c1b8-xxxx-xxxx-xxxxx-xxxxx&Version=2014-05-26&AccessKeyId=testid&Signature=OLeaidS1JvxuMvnyHOwuJ%2BuX5qY%3D&SignatureMethod=HMAC-SHA1&Timestamp=2016-02-23T12%253A46%253A24Z
  5. 使用浏览器、curl或者wget等工具发送HTTP请求。
    返回结果如下:
    <DescribeDedicatedHostsResponse>
      <PageNumber>1</PageNumber>
      <DedicatedHosts>
        <DedicatedHost>
          <DedicatedHostId>dh-2xxxxxxxxxxxxx</DedicatedHostId>
          <ChargeType>PostPaid</ChargeType>
          <Description/>
          <ResourceGroupId/>
          <SupportedInstanceTypeFamilies>
            <SupportedInstanceTypeFamily>ecs.se1ne</SupportedInstanceTypeFamily>
          </SupportedInstanceTypeFamilies>
          <Instances/>
          <Cores>32</calCores>
          <ZoneId>cn-beijing-c</ZoneId>
          <CreationTime>2018-08-13T07:59Z</CreationTime>
          <Sockets>2</Sockets>
          <Status>Available</Status>
          <DedicatedHostType>ddh.se1ne</DedicatedHostType>
          <RegionId>cn-beijing</RegionId>
          <DedicatedHostName>myDDH</DedicatedHostName>
          <SaleCycle/>
          <AutoReleaseTime/>
          <Capacity>
            <AvailableVcpus>56</AvailableVcpus>
            <TotalMemory>448.0</TotalMemory>
            <TotalVcpus>56</TotalVcpus>
            <AvailableLocalStorage>0</AvailableLocalStorage>
            <TotalLocalStorage>0</TotalLocalStorage>
            <LocalStorageCategory/>
            <AvailableMemory>448.0</AvailableMemory>
          </Capacity>
          <OperationLocks/>
          <MachineId>d7xxxxxxxxxxxxxxxxxdb</MachineId>
          <ExpiredTime>2999-09-08T16:00Z</ExpiredTime>
        </DedicatedHost>
        <DedicatedHost>
          <DedicatedHostId>dh-2xxxxxxxxxxxxx</DedicatedHostId>
          <ChargeType>PostPaid</ChargeType>
          <Description/>
          <ResourceGroupId/>
          <SupportedInstanceTypeFamilies>
            <SupportedInstanceTypeFamily>ecs.se1ne</SupportedInstanceTypeFamily>
          </SupportedInstanceTypeFamilies>
          <Instances/>
          <Cores>32</Cores>
          <ZoneId>cn-beijing-c</ZoneId>
          <CreationTime>2018-08-13T07:59Z</CreationTime>
          <Sockets>2</Sockets>
          <Status>Available</Status>
          <DedicatedHostType>ddh.se1ne</DedicatedHostType>
          <RegionId>cn-beijing</RegionId>
          <DedicatedHostName>myDDH</DedicatedHostName>
          <SaleCycle/>
          <AutoReleaseTime/>
          <Capacity>
            <AvailableVcpus>56</AvailableVcpus>
            <TotalMemory>448.0</TotalMemory>
            <TotalVcpus>56</TotalVcpus>
            <AvailableLocalStorage>0</AvailableLocalStorage>
            <TotalLocalStorage>0</TotalLocalStorage>
            <LocalStorageCategory/>
            <AvailableMemory>448.0</AvailableMemory>
          </Capacity>
          <OperationLocks/>
          <MachineId>fxxxxxxxxxxxxxxx6ca6</MachineId>
          <ExpiredTime>2999-09-08T16:00Z</ExpiredTime>
        </DedicatedHost>
      </DedicatedHosts>
      <TotalCount>2</TotalCount>
      <PageSize>10</PageSize>
      <RequestId>C9E9EA51-6B74-409E-BA40-107126A200D4</RequestId>
    </DescribeDedicatedHostsResponse>

    返回结果为XML格式。如果您在提交请求时,指定参数Format=JSON,则返回的结果为JSON格式。更多详情,请参见返回结果