阿里云产品服务会对每个访问请求进行身份验证,所以无论您使用 HTTP 还是 HTTPS 协议提交请求,都需要在请求中包含签名(Signature)信息。
签名需要您在控制台
Access Key 管理 页面获得 Access Key ID 和 Access Key Secret,进行对称加密来验证请求的发送者身份。其中,Access Key ID 用于标识访问者身份;Access Key Secret 是用于加密签名字符串和服务器端验证签名字符串的密钥,必须严格保密。
注意:阿里云提供了 Python、Java、PHP 和 C# 等语言的 SDK 及第三方 SDK,可以免去您对签名算法进行编码的麻烦。您可以从 这里 了解更多阿里云 SDK 的信息。
签名操作
您在访问时,需要按照下面的方法对请求进行签名处理。
构造规范化的请求字符串(Canonicalized Query String)。
按参数名进行排序。
按照参数名称的字典顺序对请求中所有的请求参数进行排序,包括“公共请求参数”(不包括 Signature 参数)和接口的自定义参数。
注意:当使用 GET 方法提交请求时,这些参数就是请求 URI 中的参数部分(即 URI 中“?”之后由“&”连接的部分)。
对参数名称和参数值进行 URL 编码。
名称和值要使用 UTF-8 字符集进行 URL 编码。URL 编码的规则如下:
- 字符 A~Z、a~z、0~9 以及字符“-”、“_”、“.”、“~”不编码;
- 其它字符编码成 %XY 的格式,其中 XY 是字符对应 ASCII 码的 16 进制表示。比如英文的双引号(”)对应的编码为 %22;
- 对于扩展的 UTF-8 字符,编码成 %XY%ZA… 的格式;
- 英文空格( )要编码成 %20,而不是加号(+)。
注意:一般支持URL编码的库(比如 Java 中的 java.net.URLEncoder)都是按照 “application/x-www-form-urlencoded”的 MIME 类型的规则进行编码的。实现时可以直接使用这类方式进行编码,把编码后的字符串中加号(+)替换成 %20、星号(*)替换成 %2A、%7E 替换回波浪号(~),即可得到上述规则描述的编码字符串。
对编码后的参数名称和值使用英文等号(=)进行连接。
按参数名称的字典顺序,把英文等号连接得到字符串依次使用 & 符号连接,即得到规范化请求字符串。
构造被签名字符串。
基于步骤 1 得到的规范化字符串构造用于计算签名的字符串,可参考如下规则:<divre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
- StringToSign=
- HTTPMethod + “&” +
- percentEncode(“/”) + ”&” +
- percentEncode(CanonicalizedQueryString)
其中:
- HTTPMethod 是提交请求用的 HTTP 方法,比如 GET。
- percentEncode(“/”) 是按照步骤 1.i 中描述的 URL 编码规则对字符 “/” 进行编码得到的值,即 %2F。
- percentEncode(CanonicalizedQueryString) 是对步骤 1 中构造的规范化请求字符串按步骤 1.ii 中描述的 URL 编码规则编码后得到的字符串。
计算 HMAC 值。
按照
RFC2104 的定义,使用步骤 2 得到的字符串计算签名 HMAC 值。
注意:计算签名时使用的 Key 就是您持有的 Access Key Secret 并加上一个 “&” 字符(ASCII:38),使用的哈希算法是 SHA1。
计算签名值。
按照
Base64 编码规则 把步骤 3 中的 HMAC 值编码成字符串,即得到签名值(Signature)。
添加签名。
将得到的签名值作为 Signature 参数添加到请求参数中,即完成对请求签名的过程。
注意:得到的签名值在作为最后的请求参数值提交给服务器时,要和其它参数一样,按照
RFC3986 的规则进行 URL 编码。
示例
以 DescribeRegions 为例,假设使用的 Access Key Id 为 testid, Access Key Secret 为 testsecret。 那么签名前的请求 URL 为:<divre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); color: rgb(51, 51, 51); text-transform: none; text-indent: 0px; letter-spacing: normal; overflow: auto; word-spacing: 0px; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; orphans: 2; widows: 2; font-size-adjust: none; font-stretch: normal; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;' prettyprinted?="" linenums="">
- http://ecs.aliyuncs.com/?TimeStamp=2016-02-23T12:46:24Z&Format=XML&AccessKeyId=testid&Action=DescribeRegions&SignatureMethod=HMAC-SHA1&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&Version=2014-05-26&SignatureVersion=1.0
计算得到的待签名字符串 StringToSign 为:<divre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); color: rgb(51, 51, 51); text-transform: none; text-indent: 0px; letter-spacing: normal; overflow: auto; word-spacing: 0px; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; orphans: 2; widows: 2; font-size-adjust: none; font-stretch: normal; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;' prettyprinted?="" linenums="">
- GET&%2F&AccessKeyId%3Dtestid&Action%3DDescribeRegions&Format%3DXML&SignatureMethod%3DHMAC-SHA1&SignatureNonce%3D3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&SignatureVersion%3D1.0&TimeStamp%3D2016-02-23T12%253A46%253A24Z&Version%3D2014-05-26
因为 Access Key Secret 为 testsecret,所以用于计算 HMAC 的 Key 为 testsecret&,计算得到的签名值为:<divre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); color: rgb(51, 51, 51); text-transform: none; text-indent: 0px; letter-spacing: normal; overflow: auto; word-spacing: 0px; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; orphans: 2; widows: 2; font-size-adjust: none; font-stretch: normal; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;' prettyprinted?="" linenums="">
- CT9X0VtwR86fNWSnsc6v8YGOjuE=
将签名作为 Signature 参数加入到 URL 请求中,最后得到的 URL 为:<divre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); color: rgb(51, 51, 51); text-transform: none; text-indent: 0px; letter-spacing: normal; overflow: auto; word-spacing: 0px; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; orphans: 2; widows: 2; font-size-adjust: none; font-stretch: normal; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;' prettyprinted?="" linenums="">
- http://ecs.aliyuncs.com/?SignatureVersion=1.0&Action=DescribeRegions&Format=XML&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&Version=2014-05-26&AccessKeyId=testid&Signature=CT9X0VtwR86fNWSnsc6v8YGOjuE%3D&SignatureMethod=HMAC-SHA1&TimeStamp=2016-02-23T12%3A46%3A24Z
Java 示例代码
本示例不需要依赖第三方的库包,可以直接使用。签名步骤如下所示。
构造规范化的请求字符串(排序及 URL 编码)。<divre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums=""> public static Map<String, String> splitQueryString(String url)- throws URISyntaxException, UnsupportedEncodingException {
- URI uri = new URI(url);
- String query = uri.getQuery();
- final String[] pairs = query.split("&");
- TreeMap<String, String> queryMap = new TreeMap<String, String>();
- for (String pair : pairs) {
- final int idx = pair.indexOf("=");
- final String key = idx > 0 ? pair.substring(0, idx) : pair;
- if (!queryMap.containsKey(key)) {
- queryMap.put(key, URLDecoder.decode(pair.substring(idx + 1),
- CHARSET_UTF8));
- }
- }
- return queryMap;
- }
- /** 对参数名称和参数值进行URL编码**/
- public static String generate(String method, Map<String, String> parameter,
- String accessKeySecret) throws Exception {
- String signString = generateSignString(method, parameter);
- System.out.println("signString---" + signString);
- byte[] signBytes = hmacSHA1Signature(accessKeySecret + "&", signString);
- String signature = newStringByBase64(signBytes);
- System.out.println("signature---" + signature);
- if ("POST".equals(method))
- return signature;
- return URLEncoder.encode(signature, "UTF-8");
- }
构造成待签名的字符串。<divre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
- public static String generateSignString(String httpMethod,
- Map<String, String> parameter) throws IOException {
- TreeMap<String, String> sortParameter = new TreeMap<String, String>();
- sortParameter.putAll(parameter);
- String canonicalizedQueryString = UrlUtil.generateQueryString(
- sortParameter, true);
- if (null == httpMethod) {
- throw new RuntimeException("httpMethod can not be empty");
- }
- /** 构造待签名的字符串* */
- StringBuilder stringToSign = new StringBuilder();
- stringToSign.append(httpMethod).append(SEPARATOR);
- stringToSign.append(percentEncode("/")).append(SEPARATOR);
- stringToSign.append(percentEncode(canonicalizedQueryString));
- return stringToSign.toString();
- }
计算待签名字符串的 HMAC 值。<divre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
- public static byte[] hmacSHA1Signature(String secret, String baseString)
- throws Exception {
- if (isEmpty(secret)) {
- throw new IOException("secret can not be empty");
- }
- if (isEmpty(baseString)) {
- return null;
- }
- Mac mac = Mac.getInstance("HmacSHA1");
- SecretKeySpec keySpec = new SecretKeySpec(
- secret.getBytes(CHARSET_UTF8), ALGORITHM);
- mac.init(keySpec);
- return mac.doFinal(baseString.getBytes(CHARSET_UTF8));
- }
- private static boolean isEmpty(String str) {
- return (str == null || str.length() == 0);
- }
按照 Base64 编码规则编码成字符串,获取签名值。<divre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
- public static String newStringByBase64(byte[] bytes)
- throws UnsupportedEncodingException {
- if (bytes == null || bytes.length == 0) {
- return null;
- }
- return new String(new BASE64Encoder().encode(bytes));
- }
- public static String composeStringToSign(Map<String, String> queries) {
- String[] sortedKeys = (String[]) queries.keySet()
- .toArray(new String[0]);
- Arrays.sort(sortedKeys);
- StringBuilder canonicalizedQueryString = new StringBuilder();
- for (String key : sortedKeys) { canonicalizedQueryString.append("&").append(percentEncode(key))
- .append("=")
- .append(percentEncode((String) queries.get(key)));
- }
- StringBuilder stringToSign = new StringBuilder();
- stringToSign.append("GET");
- stringToSign.append("&");
- stringToSign.append(percentEncode("/"));
- stringToSign.append("&"); stringToSign.append(percentEncode(canonicalizedQueryString.toString()
- .substring(1)));
- return stringToSign.toString();
- }
- public static String percentEncode(String value) {
- try {
- return value == null ? null : URLEncoder
- .encode(value, CHARSET_UTF8).replace("+", "%20")
- .replace("*", "%2A").replace("%7E", "~");
- } catch (Exception e) {
- }
- return "";
- }
- /**
- * get SignatureNonce
- ** */
- public static String getUniqueNonce() {
- UUID uuid = UUID.randomUUID();
- return uuid.toString();
- }
- /**
- * get timestamp
- **/
- public static String getISO8601Time() {
- Date nowDate = new Date();
- SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
- df.setTimeZone(new SimpleTimeZone(0, "GMT"));
- return df.format(nowDate);
- }
添加签名。<divre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
- public static String composeUrl(String endpoint, Map<String, String> queries)
- throws UnsupportedEncodingException {
- Map<String, String> mapQueries = queries;
- StringBuilder urlBuilder = new StringBuilder("");
- urlBuilder.append("http");
- urlBuilder.append("://").append(endpoint);
- if (-1 == urlBuilder.indexOf("?")) {
- urlBuilder.append("/?");
- }
- urlBuilder.append(concatQueryString(mapQueries));
- return urlBuilder.toString();
- }
- public static String concatQueryString(Map<String, String> parameters)
- throws UnsupportedEncodingException {
- if (null == parameters) {
- return null;
- }
- StringBuilder urlBuilder = new StringBuilder("");
- for (Map.Entry<String, String> entry : parameters.entrySet()) {
- String key = (String) entry.getKey();
- String val = (String) entry.getValue();
- urlBuilder.append(encode(key));
- if (val != null) {
- urlBuilder.append("=").append(encode(val));
- }
- urlBuilder.append("&");
- }
- int strIndex = urlBuilder.length();
- if (parameters.size() > 0) {
- urlBuilder.deleteCharAt(strIndex - 1);
- }
- return urlBuilder.toString();
- }
- public static String encode(String value)
- throws UnsupportedEncodingException {
- return URLEncoder.encode(value, "UTF-8");
- }
- }
API 调用示例
示例 1
<pre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); color: rgb(51, 51, 51); text-transform: none; text-indent: 0px; letter-spacing: normal; overflow: auto; word-spacing: 0px; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; orphans: 2; widows: 2; font-size-adjust: none; font-stretch: normal; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;' prettyprinted?="" linenums="">
- /*
- * Demo_01: Build a immutable signature instance and print out;
- * The Signature's toString() function will invoke signature's get() function.
- */
- out("output: " +
- Signature.newBuilder()
- .method(The.HTTP.GET.method())
- .url(URL)
- .secret(The.API.secret())
- .build()
- );
示例 2
<pre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); color: rgb(51, 51, 51); text-transform: none; text-indent: 0px; letter-spacing: normal; overflow: auto; word-spacing: 0px; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; orphans: 2; widows: 2; font-size-adjust: none; font-stretch: normal; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;' prettyprinted?="" linenums="">
- /*
- * Demo_02: Build a real signature and compose a request url to invoke DescribeRegions api call;
- */
- 对一个真实的 API URL 进行签名
- final String ACTION = "DescribeRegions";
- final String API_URL = The.API.build(ACTION);
- out(httpGet(Signature.newBuilder()
- .method(The.HTTP.GET.method())
- .url(API_URL)
- .secret(The.API.secret())
- .build()
- .compose())
- );
为了方便您快速使用签名机制,您可以在
这里 下载完整的示例代码。
注意:您需要将 ACCESS 和 SECRET 替换为您的 Access Key ID 和 Access Key Secret。