為保證API的安全調用,在調用API時,RTC會對每個API請求通過簽名(Signature)進行身份驗證。無論您使用HTTP還是HTTPS協議提交請求,都需要在請求中包含簽名信息。

簽名格式

RESTful API需要按照如下格式在API請求頭(RequestHeader)中添加Authorization參數進行簽名。

Authorization:acs:AccessKeyId:Singature
  • acs:Alibaba Cloud Service的縮寫,固定標識不可修改。
  • AccessKeyId:調用者調用API所用的密鑰ID。
  • Signature:使用AccessKey Secret對請求進行對稱加密的簽名。

簽名方式

簽名算法遵循HMAC-SHA1規范,使用AccessSecret對編碼、排序后的整個請求串計算HMAC值作為簽名。簽名的元素是請求自身的一些參數,由于每個API請求內容不同,所以簽名的結果也不盡相同。

Signature = Base64( HMAC-SHA1( AccessSecret, UTF-8-Encoding-Of(StringToSign)) )

簽名步驟

  1. 構建待簽名字符串。

    待簽名字符串(StringToSign)是API請求拼裝的字符串,用于計算簽名。待簽名字符串必須按照以下順序構造:

    StringToSign = 
        // HTTP協議Header
        HTTP-Verb + "\n" +
        Accept + "\n" +
        Content-MD5 + "\n" +//Body的MD5值放在此處
        Content-Type + "\n" +
        Date + "\n" +
        // 阿里云協議Header
        CanonicalizedHeaders +
        // 規范資源
        CanonicalizedResource
    說明

    原始請求示例:

    POST /api/call/describeCallList?xxx=xxx HTTP/1.1
    Host: vdc.cn-shenzhen.aliyuncs.com
    Accept: application/json
    Content-MD5: 
    Content-Type: application/json
    Date: Thu, 22 Feb 2018 07:46:12 GMT 
    x-acs-signature-nonce: 550e8400-e29b-41d4-a716-446655440000
    x-acs-signature-method: HMAC-SHA1
    x-acs-action: DescribeCallList
    x-acs-version: 2020-12-14

    規范請求StringToSign示例:

    POST
    application/json
    
    application/json
    Thu, 22 Feb 2018 07:46:12 GMT
    x-acs-action:DescribeCallList
    x-acs-signature-nonce:550e8400-e29b-41d4-a716-446655440000
    x-acs-signature-method:HMAC-SHA1
    x-acs-version:2016-01-02
    /api/call/describeCallList?xxx=xxx
  2. 添加簽名。

    將計算好的簽名Signature按照如下格式添加到RequestHeader中。

    addHeader("Authorization", "acs " + {AccessKeyId} + ":" + {Signature})

HTTP協議Header

計算簽名必須包含以下參數,并按字典順序排列,如果值不存在需要用\n補齊。

  • Accept:客戶端需要的返回值類型,取值:application/json或application/xml。
  • Content-MD5:HTTP協議消息體的128-bit MD5散列值轉換成BASE64編碼的結果。
  • Content-Type:RFC 2616中定義的HTTP請求內容類型。
  • Date:HTTP 1.1協議中規定的GMT時間,例如:Wed, 05 Sep. 2012 23:00:00 GMT。

原始Header示例:

Accept: application/json
Content-MD5: 
Content-Type: application/json
Date: Thu, 22 Feb 2018 07:46:12 GMT

規范后Header示例:

application/json

application/json
Thu, 22 Feb 2018 07:46:12 GMT

阿里云協議Header(CanonicalizedHeaders)

阿里云規范頭,非標準HTTP頭部信息,是請求中出現的以x-acs-為前綴的參數。請求中必須包含以下參數:

  • x-acs-signature-nonce:唯一隨機數,用于防止網絡重放攻擊。在不同請求間要使用不同的隨機數值。
  • x-acs-signature-method:用戶簽名方式,目前支持HMAC-SHA1。
  • x-acs-action:API名稱。
  • x-acs-version:API版本號。

構造阿里云規范頭,需要完成以下操作:

  1. 將所有以x-acs-為前綴的HTTP請求頭的名字轉換成小寫字母。例如將X-acs-Action: DescribeCallList轉換成x-acs-action: DescribeCallList。
  2. 將上一步得到的所有HTTP阿里云規范頭按照字典序進行升序排列。
  3. 刪除請求頭和內容之間分隔符兩端出現的任何空格。例如將x-acs-action: DescribeCallList轉換成x-acs-action:DescribeCallList。
  4. 將所有的頭和內容用\n分隔符分隔拼成最后的CanonicalizedHeaders。

原始Header示例:

x-acs-signature-nonce: 550e8400-e29b-41d4-a716-446655440000
x-acs-signature-method: HMAC-SHA1
x-acs-action: DescribeCallList
x-acs-version: 2020-12-14

規范后Header示例:

x-acs-action:DescribeCallList
x-acs-signature-nonce:550e8400-e29b-41d4-a716-446655440000
x-acs-signature-method:HMAC-SHA1
x-acs-version:2020-12-14

規范資源(CanonicalizedResource)

CanonicalizedResource表示想要訪問資源的規范描述,需要將子資源和query參數一同按照字典序,從小到大排列并以&為分隔符生成子資源字符串(?后的所有參數)。

原始請求示例:

/api/call/describeCallList?yyy=yyy&xxx=xxx

規范后請求示例:

/api/call/describeCallList?xxx=xxx&yyy=yyy

簽名示例

  • CommonRequest方式

    目前數據服務還未生成自己的SDK,可以使用CommonRequest來統一調用(CommonRequest內部已經實現了簽名),方法如下所示:

    1. 引入Maven依賴。
      <dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>aliyun-java-sdk-core</artifactId>
        <version>4.5.6</version>
      </dependency>
    2. 代碼示例。
      CommonRequest request = new CommonRequest();
      request.setSysMethod(MethodType.POST);
      request.setSysProtocol(ProtocolType.HTTP);
      request.setSysDomain("vdc.cn-shenzhen.aliyuncs.com");
      request.setSysVersion("2020-12-14");
      request.setSysUriPattern("/api/call/describeCallList");
      request.putQueryParameter("AppId", "pdtkb2qy");
      request.putQueryParameter("PageNo", "1");
      request.putQueryParameter("PageSize", "10");
      long startTs = System.currentTimeMillis() / 1000 - 24 * 60 * 60 * 3;
      long endTs = System.currentTimeMillis()/1000;
      request.putQueryParameter("StartTs", String.valueOf(startTs));
      request.putQueryParameter("EndTs", String.valueOf(endTs));
      
      try {
          DefaultProfile profile = DefaultProfile.getProfile("cn-shenzhen",
                                                             yourAccessKey,
                                                             yourSecret);
          CommonResponse response = new DefaultAcsClient(profile).getCommonResponse(request);
          System.out.println(response.getData());
      } catch (Exception e) {
          e.printStackTrace();
      }
  • 自行實現簽名方式
    String host = "https://vdc.cn-shenzhen.aliyuncs.com";
    String action = "DescribeCallList";
    String version = "2020-12-14";
    
    long startTs = System.currentTimeMillis()/1000 - 24 * 60 * 60 * 3;
    long endTs = System.currentTimeMillis() / 1000;
    
    // 構建接口自定義參數
    Map<String, String> params = Maps.newTreeMap(String::compareTo);
    params.put("AppId", "pdtkb2qy");
    params.put("StartTs", String.valueOf(startTs));
    params.put("EndTs", String.valueOf(endTs));
    params.put("PageNo", "1");
    params.put("PageSize", "10");
    
    // 規范資源
    List<String> queryParams = Lists.newArrayList();
    params.forEach((key, value) -> queryParams.add(key + "=" + value));
    String canonicalizedResource = "/api/call/describeCallList?" + StringUtils.join(queryParams , "&");
    
    // 構建阿里云協議Header
    HttpHeaders headers = new HttpHeaders();
    String generateRandom = UUID.randomUUID().toString();
    headers.set("x-acs-action", action);
    headers.set("x-acs-version", version);
    headers.set("x-acs-signature-nonce", generateRandom);
    headers.set("x-acs-signature-method", "HMAC-SHA1");
    headers.set("x-acs-signature-version", "1.0");
    List<String> canonicalizedHeaders = Lists.newArrayList();
    headers.forEach((k, v) -> canonicalizedHeaders.add(k + ":" + v.get(0)));
    canonicalizedHeaders.sort(String::compareTo);
    String canonicalizedHeaderStr = canonicalizedHeaders.stream()
        .map(value -> StringUtils.trim(value) + "\n")
        .collect(Collectors.joining());
    
    // 其他Http協議Header公參
    SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
    dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
    String currentDate = dateFormat.format(new Date());
    headers.set("Date", currentDate);
    MediaType accept = MediaType.APPLICATION_JSON;
    headers.setAccept(Collections.singletonList(accept));
    MediaType contentType = MediaType.APPLICATION_JSON;
    headers.setContentType(contentType);
    //      StringToSign =
    //          HTTP-Verb + "\n" +
    //          Accept + “\n” +
    //          Content-MD5 + "\n" +
    //          Content-Type + "\n" +
    //          Date + "\n" +
    //          CanonicalizedHeaders + CanonicalizedResource
    List<String> signString = Lists.newArrayList();
    signString.add(HttpMethod.POST.name());
    signString.add(accept.toString());
    signString.add("");
    signString.add(contentType.toString());
    signString.add(currentDate);
    String stringToSign = StringUtils.join(signString, "\n") + "\n" + canonicalizedHeaderStr + canonicalizedResource;
    
    try {
        // 簽名
        // 計算待簽名字符串的HMAC值
        String algorithm = "HmacSHA1";
        SecretKeySpec signKey = new SecretKeySpec(yourSecret.getBytes(), algorithm);
        Mac mac = Mac.getInstance(algorithm);
        mac.init(signKey);
        byte[] bytes = mac.doFinal(stringToSign.getBytes());
        // 按照 Base64 編碼規則將計算得到的HMAC值編碼成字符串,得到最終簽名值(Signature)
        String sign = new BASE64Encoder().encode(bytes);
    
        String authorization = "acs " + yourAccessKey + ":" + sign;
        headers.set("Authorization", authorization);
        HttpEntity<String> entity = new HttpEntity<>(headers);
    
        String reqUrl = host + canonicalizedResource;
        ResponseEntity<JSONObject> exchange = new RestTemplate().exchange(reqUrl, HttpMethod.POST, entity, JSONObject.class);
        if (exchange.hasBody()) {
            System.out.println(exchange.getBody());
        }
    } catch (Exception e) {
        e.printStackTrace();
    }