为保证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();
    }