為保證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)) )
簽名步驟
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版本號。
構造阿里云規范頭,需要完成以下操作:
- 將所有以x-acs-為前綴的HTTP請求頭的名字轉換成小寫字母。例如將X-acs-Action: DescribeCallList轉換成x-acs-action: DescribeCallList。
- 將上一步得到的所有HTTP阿里云規范頭按照字典序進行升序排列。
- 刪除請求頭和內容之間分隔符兩端出現的任何空格。例如將x-acs-action: DescribeCallList轉換成x-acs-action:DescribeCallList。
- 將所有的頭和內容用
\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內部已經實現了簽名),方法如下所示:
- 引入Maven依賴。
<dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.5.6</version> </dependency>
- 代碼示例。
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(); }
- 引入Maven依賴。
- 自行實現簽名方式
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(); }