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