物联网络管理平台会对每个接口访问请求的发送者进行身份验证,所以无论使用 HTTP 还是 HTTPS 协议提交请求,都需要在请求中包含签名(Signature)信息。
签名时,您需在控制台 AccessKey 管理页面查看您的阿里云账号的 AccessKeyId 和 AccessKeySecret,然后进行对称加密。其中,AccessKeyId 用于标识访问者身份;AccessKeySecret 是用于加密签名字符串和服务器端验证签名字符串的密钥,必须严格保密。
请按照下面的方法对请求进行签名:
- 构造规范化的请求字符串(Canonicalized Query String)。
- 排序参数。
按参数名的字典顺序,对请求参数进行排序,包括公共参数(不包括 Signature 参数)和接口的自定义参数。
说明 当使用GET方法提交请求时,这些参数就是请求URL中的参数部分,即URL中问号(?)之后由并列符号(&)连接的部分。 - 对参数名称和参数值进行 URL 编码。
使用UTF-8字符集按照 RFC3986 规则编码请求参数名和参数值。编码规则如下。
- 字符大写字母(A~Z)、小写字母(a~z)、数字(0~9)以及半字线(-)、下划线(_)、点号(.)、波浪线(~)不编码。
- 其它字符编码成
%XY
的格式,其中XY
是字符对应ASCII码的16进制表示。例如英文的双引号"
对应的编码为%22
。 - 扩展的 UTF-8 字符,编码成
%XY%ZA...
的格式。 - 英文空格要编码成
%20
,而不是加号+
。
该编码方式与
application/x-www-form-urlencoded
MIME 格式编码算法相似,但又有所不同。如果您使用的是 Java 标准库中的
java.net.URLEncoder
,可以先用encode
编码,随后将编码后的字符中加号+
替换为%20
、星号*
替换为%2A
、%7E
替换回波浪号~
,即可得到上述规则描述的编码字符串。private static final String ENCODING = "UTF-8"; private static String percentEncode(String value) throws UnsupportedEncodingException { return value != null ? URLEncoder.encode(value, ENCODING).replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null; }
- 使用等号
=
连接编码后的请求参数名和参数值。 - 使用与号
&
连接编码后的请求参数。参数排序与步骤“排序参数”的描述一致。
完成后,即获得规范化请求字符串(CanonicalizedQueryString)。
- 排序参数。
- 构造签名字符串。
可以使用
percentEncode
处理步骤 1 得到的规范化字符串,构造签名字符串。可参考如下规则。String stringToSign = httpMethod + "&" + // httpMethod: 发送请求所采用的 HTTP 方法,例如 GET。 percentEncode("/") + "&" + // percentEncode("/"): 字符'/'UTF-8 编码得到的值,即 %2F。 percentEncode(canonicalizedQueryString) // 您的规范化请求字符串。
- 计算 HMAC 值。
按照RFC2104的定义,使用步骤 2 得到的字符串
stringToSign
计算签名 HMAC 值。下列伪代码演示了获得 HMAC 值的方法。HMAC-Value = HMAC-SHA1 ( AccessSecret, UTF-8-Encoding-Of ( StringToSign ) )
说明 计算HMAC 值时,使用的 Key 就是您的AccessKeySecret并加上一个与号&
字符(ASCII:38)。使用的哈希算法是SHA1。 - 计算签名值。
按照 Base64 编码规则把步骤 3 中的HMAC值编码成字符串,即得到签名值(Signature)。
- 添加签名。
将得到的签名值作为Signature参数,按照RFC3986的规则进行URL编码后,再添加到请求参数中,即完成对请求签名的过程。
签名示例
以调用API方法GetGateway为例。假设AccessKeyId = testid
,AccessKeySecret = testsecret
。
- 签名前的请求URL:
https://linkwan.cn-shanghai.aliyuncs.com/ ?Format=JSON &Version=2019-01-20 &SignatureMethod=HMAC-SHA1 &SignatureNonce=15215528852396 &SignatureVersion=1.0 &AccessKeyId=testid &Timestamp=2019-01-20T12:00:00Z &RegionId=cn-shanghai &Action=GetGateway &GwEui=0000000000000000
- 计算得到的待签名字符串
StringToSign
:GET&%2F&AccessKeyId%3Dtestid&Action%3DGetGateway&Format%3DJSON&GwEui%3D0000000000000000&RegionId%3Dcn-shanghai&SignatureMethod%3DHMAC-SHA1&SignatureNonce%3D15215528852396&SignatureVersion%3D1.0&Timestamp%3D2019-01-20T12%253A00%253A00Z&Version%3D2019-01-20
- 计算签名值。
因为
AccessKeySecret = testsecret
,用于计算的 Key 为testsecret&
,计算得到的签名值为:yqWsF0aPGrECmuwTfALUIl0JM9M%3D
- 将签名作为 Signature 参数加入到 URL 请求中,最后得到的 URL 为:
https://linkwan.cn-shanghai.aliyuncs.com/ ?Format=JSON &Version=2019-01-20 &Signature=yqWsF0aPGrECmuwTfALUIl0JM9M%3D &SignatureMethod=HMAC-SHA1 &SignatureNonce=15215528852396 &SignatureVersion=1.0 &AccessKeyId=testid &Timestamp=2019-01-20T12:00:00Z &RegionId=cn-shanghai &Action=GetGateway &GwEui=0000000000000000
Java 代码示例
以下为签名的Java Demo供您参考。
- Config.java
package aliyun.signature; /** * API 签名配置。 * * @author Alibaba Cloud * @date 2019/01/20 */ public class Config { /** * 阿里云账号的 Access Key Id。 */ public static final String ACCESS_KEY_ID = "testid"; /** * 阿里云账号的 Access Key Secret。 */ public static final String ACCESS_KEY_SECRET = "testsecret"; /** * UTF-8 字符集。 */ public static final String CHARSET_UTF8 = "utf8"; }
- UrlUtil.java
package aliyun.signature; import java.net.URLEncoder; import java.util.Map; /** * URL 处理工具。 * * @author Alibaba Cloud * @date 2019/01/20 */ public class UrlUtil { /** * UTF-8 编码。 */ private final static String CHARSET_UTF8 = "utf8"; /** * 编码 URL。 * @param url 要编码的 URL。 * @return 编码后的 URL。 */ public static String urlEncode(String url) { if (url != null && !url.isEmpty()) { try { url = URLEncoder.encode(url, "UTF-8"); } catch (Exception e) { System.out.println("Url encoding error:" + e.getMessage()); } } return url; } /** * 规范化请求串。 * @param params 请求中所有参数的 KV 对。 * @param shouldEncodeKv 是否需要编码 KV 对中的文本。 * @return 规范化的请求串。 */ public static String canonicalizeQueryString(Map<String, String> params, boolean shouldEncodeKv) { StringBuilder canonicalizedQueryString = new StringBuilder(); for (Map.Entry<String, String> entry : params.entrySet()) { if (shouldEncodeKv) { canonicalizedQueryString.append(percentEncode(entry.getKey())) .append("=") .append(percentEncode(entry.getValue())) .append("&"); } else { canonicalizedQueryString.append(entry.getKey()) .append("=") .append(entry.getValue()) .append("&"); } } if (canonicalizedQueryString.length() > 1) { canonicalizedQueryString.setLength(canonicalizedQueryString.length() - 1); } return canonicalizedQueryString.toString(); } /** * 对原文进行百分号编码。 * @param text 原文。 * @return 编码结果。 */ public static String percentEncode(String text) { try { return text == null ? null : URLEncoder.encode(text, CHARSET_UTF8) .replace("+", "%20") .replace("*", "%2A") .replace("%7E", "~"); } catch (Exception e) { System.out.println("Percentage encoding error:" + e.getMessage()); } return ""; } }
- SignatureUtils.java
package aliyun.signature; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.Map; import java.util.TreeMap; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; /** * API 签名工具。 * * @author Alibaba Cloud * @date 2019/01/20 */ public class SignatureUtils { private final static String CHARSET_UTF8 = "utf8"; private final static String ALGORITHM = "UTF-8"; private final static String SEPARATOR = "&"; private final static String METHOD_NAME_POST = "POST"; /** * 分解请求串中的参数。 * @param url 原始 URL。 * @return 分解后的参数名、参数值对的映射 */ 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; } /** * 计算签名名转译成合适的编码。 * @param httpMethod HTTP 请求的 Method。 * @param parameter 原始请求串中参数名、参数值对的映射。 * @param accessKeySecret 阿里云账号的 Access Key Secret。 * @return 转译成合适编码的签名。 */ public static String generate(String httpMethod, Map<String, String> parameter, String accessKeySecret) throws Exception { String stringToSign = generateStringToSign(httpMethod, parameter); System.out.println("signString---" + stringToSign); byte[] signBytes = hmacSHA1Signature(accessKeySecret + "&", stringToSign); String signature = newStringByBase64(signBytes); if (signature == null) { return ""; } System.out.println("signature----" + signature); if (METHOD_NAME_POST.equals(httpMethod)) { return signature; } return URLEncoder.encode(signature, ALGORITHM); } /** * 计算签名中间产物 StringToSign。 * @param httpMethod HTTP 请求的 Method。 * @param parameter 原始请求串中参数名、参数值对的映射。 * @return 签名中间产物 StringToSign。 */ public static String generateStringToSign(String httpMethod, Map<String, String> parameter) throws IOException { TreeMap<String, String> sortParameter = new TreeMap<String, String>(parameter); String canonicalizedQueryString = UrlUtil.canonicalizeQueryString(sortParameter, true); if (httpMethod == null || httpMethod.isEmpty()) { 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(); } /** * 对原文进行百分号编码处理。 * @param text 要处理的原文。 * @return 处理后的百分号编码。 */ public static String percentEncode(String text) { try { return text == null ? null : URLEncoder.encode(text, CHARSET_UTF8) .replace("+", "%20") .replace("*", "%2A") .replace("%7E", "~"); } catch (Exception e) { System.out.println("Percentage encoding error:" + e.getMessage()); } return ""; } /** * HMAC-SHA1 键控散列。 * @param secret HMAC-SHA1 使用的 Secret。 * @param baseString 原文。 * @return 散列值。 */ public static byte[] hmacSHA1Signature(String secret, String baseString) throws Exception { if (secret == null || secret.isEmpty()) { throw new IOException("secret can not be empty"); } if (baseString == null || baseString.isEmpty()) { 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)); } /** * Base 64 编码。 * @param bytes 原文。 * @return Base 64 编码。 */ public static String newStringByBase64(byte[] bytes) throws UnsupportedEncodingException { if (bytes == null || bytes.length == 0) { return null; } return new String(Base64.encodeBase64(bytes, false), CHARSET_UTF8); } }
- DemoApplication.java
package aliyun.signature; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; /** * API 签名 Demo 主程序。 * * @author Alibaba Cloud * @date 2019/01/20 */ public class DemoApplication { /** * 1. 需求修改 Config.java 中的 Access Key 信息。 * 2. 建议使用方法二,所有参数都需要一一填写。 * 3. "最终 Signature"才是你需要的签名最终结果。 * @param args ... */ public static void main(String[] args) throws UnsupportedEncodingException { // 方法一。 System.out.println("方法一:"); String str = "GET&%2F&AccessKeyId%3D" + Config.ACCESS_KEY_ID + "&Action%3DGetGateway&Format%3DJSON&GwEui%3D" + "0000000000000000&RegionId%3Dcn-shanghai&Signa" + "tureMethod%3DHMAC-SHA1&SignatureNonce%3D1521552" + "8852396&SignatureVersion%3D1.0&Timestamp%3D20" + "19-01-20T12%253A00%253A00Z&Version%3D2019-01-20"; byte[] signBytes; try { signBytes = SignatureUtils.hmacSHA1Signature(Config.ACCESS_KEY_SECRET + "&", str.toString()); String signature = SignatureUtils.newStringByBase64(signBytes); System.out.println("signString---" + str); System.out.println("signature----" + signature); System.out.println("最终signature:" + URLEncoder.encode(signature, Config.CHARSET_UTF8)); } catch (Exception e) { e.printStackTrace(); } System.out.println(); // 方法二。 System.out.println("方法二:"); Map<String, String> map = new HashMap<String, String>(); // 公共参数。 map.put("Format", "JSON"); map.put("Version", "2019-01-20"); map.put("AccessKeyId", Config.ACCESS_KEY_ID); map.put("SignatureMethod", "HMAC-SHA1"); map.put("Timestamp", "2019-01-20T12:00:00Z"); map.put("SignatureVersion", "1.0"); map.put("SignatureNonce", "15215528852396"); map.put("RegionId", "cn-shanghai"); map.put("Action", "GetGateway"); // 请求参数。 map.put("GwEui", "0000000000000000"); try { String signature = SignatureUtils.generate("GET", map, Config.ACCESS_KEY_SECRET); System.out.println("最终signature:" + signature); } catch (Exception e) { e.printStackTrace(); } System.out.println(); } }