全部产品
云市场

签名机制

更新时间:2019-03-26 14:57:26

物联网络管理平台会对每个接口访问请求的发送者进行身份验证,所以无论使用 HTTP 还是 HTTPS 协议提交请求,都需要在请求中包含签名(Signature)信息。

签名算法

签名时,您需在控制台 AccessKey 管理页面查看您的阿里云账号的 AccessKeyId 和 AccessKeySecret,然后进行对称加密。其中,AccessKeyId 用于标识访问者身份;AccessKeySecret 是用于加密签名字符串和服务器端验证签名字符串的密钥,必须严格保密。

说明 物联网络管理平台提供了 Java 语言的云端 SDK。使用平台 SDK,可以免去签名过程。请参见 Java SDK 使用说明及 SDK 的使用说明。

请按照下面的方法对请求进行签名:

  1. 构造规范化的请求字符串(Canonicalized Query String)。

    1. 排序参数。

      按参数名的字典顺序,对请求参数进行排序,包括公共请求参数(不包括 Signature 参数)和接口的自定义参数。

      说明 当使用 GET 方法提交请求时,这些参数就是请求 URL 中的参数部分,即 URL 中 ? 之后由 & 连接的部分。

    2. 对参数名称和参数值进行 URL 编码。

      使用UTF-8字符集按照 RFC3986 规则编码请求参数名和参数值。编码规则如下:

      • 字符A~Za~z0~9以及字符-_.~不编码。

      • 其它字符编码成%XY的格式,其中XY是字符对应 ASCII 码的 16 进制表示。比如英文的双引号"对应的编码为%22

      • 扩展的 UTF-8 字符,编码成%XY%ZA...的格式。

      • 英文空格要编码成%20,而不是加号+

        该编码方式与application/x-www-form-urlencodedMIME 格式编码算法相似,但又有所不同。

        如果您使用的是 Java 标准库中的java.net.URLEncoder,可以先用encode编码,随后将编码后的字符中加号+替换为%20、星号*替换为%2A%7E替换回波浪号~,即可得到上述规则描述的编码字符串。

        1. private static final String ENCODING = "UTF-8";
        2. private static String percentEncode(String value) throws UnsupportedEncodingException {
        3. return value != null ? URLEncoder.encode(value, ENCODING).replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null;
        4. }
    3. 使用等号=连接编码后的请求参数名和参数值。

    4. 使用与号&连接编码后的请求参数。参数排序与步骤“排序参数”的描述一致。

      完成后,即获得规范化请求字符串(CanonicalizedQueryString)。

  2. 构造签名字符串。

    可以使用percentEncode处理步骤 1 得到的规范化字符串,构造签名字符串。可参考如下规则:

    1. String stringToSign = httpMethod + "&" + // httpMethod: 发送请求所采用的 HTTP 方法,例如 GET。
    2. percentEncode("/") + "&" + // percentEncode("/"): 字符'/'UTF-8 编码得到的值,即 %2F。
    3. percentEncode(canonicalizedQueryString) // 您的规范化请求字符串。
  3. 计算 HMAC 值。

    按照 RFC2104 的定义,使用步骤 2 得到的字符串stringToSign计算签名 HMAC 值。下列伪代码演示了获得 HMAC 值的方法。

    1. HMAC-Value = HMAC-SHA1 ( AccessSecret, UTF-8-Encoding-Of ( StringToSign ) )

    说明 计算 HMAC 值时,使用的 Key 就是您的 AccessKeySecret 并加上一个与号&字符(ASCII:38)。使用的哈希算法是 SHA1。

  4. 计算签名值。

    按照 Base64 编码规则把步骤 3 中的 HMAC 值编码成字符串,即得到签名值(Signature)。

  5. 添加签名。

    将得到的签名值作为 Signature 参数,按照RFC3986的规则进行 URL 编码后,再添加到请求参数中,即完成对请求签名的过程。

签名示例

以调用 API 方法 GetGateway 为例。假设AccessKeyId = testidAccessKeySecret = testsecret

  1. 签名前的请求URL:

    1. https://linkwan.cn-shanghai.aliyuncs.com/
    2. ?Format=JSON
    3. &Version=2019-01-20
    4. &SignatureMethod=HMAC-SHA1
    5. &SignatureNonce=15215528852396
    6. &SignatureVersion=1.0
    7. &AccessKeyId=testid
    8. &Timestamp=2019-01-20T12:00:00Z
    9. &RegionId=cn-shanghai
    10. &Action=GetGateway
    11. &GwEui=0000000000000000
  2. 计算得到的待签名字符串StringToSign

    1. GET&%2F&AccessKeyId%3Dtestid%26Action%3DGetGateway%26Format%3DJSON%26GwEui%3D0000000000000000%26RegionId%3Dcn-shanghai%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D15215528852396%26SignatureVersion%3D1.0%26Timestamp%3D2019-01-20T12%253A00%253A00Z%26Version%3D2019-01-20
  3. 计算签名值。

    因为AccessKeySecret = testsecret,用于计算的 Key 为testsecret&,计算得到的签名值为:

    1. yqWsF0aPGrECmuwTfALUIl0JM9M%3D
  4. 将签名作为 Signature 参数加入到 URL 请求中,最后得到的 URL 为:

    1. https://linkwan.cn-shanghai.aliyuncs.com/
    2. ?Format=JSON
    3. &Version=2019-01-20
    4. &Signature=yqWsF0aPGrECmuwTfALUIl0JM9M%3D
    5. &SignatureMethod=HMAC-SHA1
    6. &SignatureNonce=15215528852396
    7. &SignatureVersion=1.0
    8. &AccessKeyId=testid
    9. &Timestamp=2019-01-20T12:00:00Z
    10. &RegionId=cn-shanghai
    11. &Action=GetGateway
    12. &GwEui=0000000000000000

Java 代码示例

以下为签名的 Java Demo 供您参考。

  1. Config.java

    1. package aliyun.signature;
    2. /**
    3. * API 签名配置。
    4. *
    5. * @author Alibaba Cloud
    6. * @date 2019/01/20
    7. */
    8. public class Config {
    9. /**
    10. * 阿里云账号的 Access Key Id。
    11. */
    12. public static final String ACCESS_KEY_ID = "testid";
    13. /**
    14. * 阿里云账号的 Access Key Secret。
    15. */
    16. public static final String ACCESS_KEY_SECRET = "testsecret";
    17. /**
    18. * UTF-8 字符集。
    19. */
    20. public static final String CHARSET_UTF8 = "utf8";
    21. }
  2. UrlUtil.java

    1. package aliyun.signature;
    2. import java.net.URLEncoder;
    3. import java.util.Map;
    4. /**
    5. * URL 处理工具。
    6. *
    7. * @author Alibaba Cloud
    8. * @date 2019/01/20
    9. */
    10. public class UrlUtil {
    11. /**
    12. * UTF-8 编码。
    13. */
    14. private final static String CHARSET_UTF8 = "utf8";
    15. /**
    16. * 编码 URL。
    17. * @param url 要编码的 URL。
    18. * @return 编码后的 URL。
    19. */
    20. public static String urlEncode(String url) {
    21. if (url != null && !url.isEmpty()) {
    22. try {
    23. url = URLEncoder.encode(url, "UTF-8");
    24. } catch (Exception e) {
    25. System.out.println("Url encoding error:" + e.getMessage());
    26. }
    27. }
    28. return url;
    29. }
    30. /**
    31. * 规范化请求串。
    32. * @param params 请求中所有参数的 KV 对。
    33. * @param shouldEncodeKv 是否需要编码 KV 对中的文本。
    34. * @return 规范化的请求串。
    35. */
    36. public static String canonicalizeQueryString(Map<String, String> params, boolean shouldEncodeKv) {
    37. StringBuilder canonicalizedQueryString = new StringBuilder();
    38. for (Map.Entry<String, String> entry : params.entrySet()) {
    39. if (shouldEncodeKv) {
    40. canonicalizedQueryString.append(percentEncode(entry.getKey()))
    41. .append("=")
    42. .append(percentEncode(entry.getValue()))
    43. .append("&");
    44. } else {
    45. canonicalizedQueryString.append(entry.getKey())
    46. .append("=")
    47. .append(entry.getValue())
    48. .append("&");
    49. }
    50. }
    51. if (canonicalizedQueryString.length() > 1) {
    52. canonicalizedQueryString.setLength(canonicalizedQueryString.length() - 1);
    53. }
    54. return canonicalizedQueryString.toString();
    55. }
    56. /**
    57. * 对原文进行百分号编码。
    58. * @param text 原文。
    59. * @return 编码结果。
    60. */
    61. public static String percentEncode(String text) {
    62. try {
    63. return text == null
    64. ? null
    65. : URLEncoder.encode(text, CHARSET_UTF8)
    66. .replace("+", "%20")
    67. .replace("*", "%2A")
    68. .replace("%7E", "~");
    69. } catch (Exception e) {
    70. System.out.println("Percentage encoding error:" + e.getMessage());
    71. }
    72. return "";
    73. }
    74. }
  3. SignatureUtils.java

    1. package aliyun.signature;
    2. import java.io.IOException;
    3. import java.io.UnsupportedEncodingException;
    4. import java.net.URI;
    5. import java.net.URISyntaxException;
    6. import java.net.URLDecoder;
    7. import java.net.URLEncoder;
    8. import java.util.Map;
    9. import java.util.TreeMap;
    10. import javax.crypto.Mac;
    11. import javax.crypto.spec.SecretKeySpec;
    12. import org.apache.commons.codec.binary.Base64;
    13. /**
    14. * API 签名工具。
    15. *
    16. * @author Alibaba Cloud
    17. * @date 2019/01/20
    18. */
    19. public class SignatureUtils {
    20. private final static String CHARSET_UTF8 = "utf8";
    21. private final static String ALGORITHM = "UTF-8";
    22. private final static String SEPARATOR = "&";
    23. private final static String METHOD_NAME_POST = "POST";
    24. /**
    25. * 分解请求串中的参数。
    26. * @param url 原始 URL。
    27. * @return 分解后的参数名、参数值对的映射
    28. */
    29. public static Map<String, String> splitQueryString(String url)
    30. throws URISyntaxException,
    31. UnsupportedEncodingException {
    32. URI uri = new URI(url);
    33. String query = uri.getQuery();
    34. final String[] pairs = query.split("&");
    35. TreeMap<String, String> queryMap = new TreeMap<String, String>();
    36. for (String pair : pairs) {
    37. final int idx = pair.indexOf("=");
    38. final String key = idx > 0 ? pair.substring(0, idx) : pair;
    39. if (!queryMap.containsKey(key)) {
    40. queryMap.put(key, URLDecoder.decode(pair.substring(idx + 1), CHARSET_UTF8));
    41. }
    42. }
    43. return queryMap;
    44. }
    45. /**
    46. * 计算签名名转译成合适的编码。
    47. * @param httpMethod HTTP 请求的 Method。
    48. * @param parameter 原始请求串中参数名、参数值对的映射。
    49. * @param accessKeySecret 阿里云账号的 Access Key Secret。
    50. * @return 转译成合适编码的签名。
    51. */
    52. public static String generate(String httpMethod, Map<String, String> parameter, String accessKeySecret)
    53. throws Exception {
    54. String stringToSign = generateStringToSign(httpMethod, parameter);
    55. System.out.println("signString---" + stringToSign);
    56. byte[] signBytes = hmacSHA1Signature(accessKeySecret + "&", stringToSign);
    57. String signature = newStringByBase64(signBytes);
    58. if (signature == null) {
    59. return "";
    60. }
    61. System.out.println("signature----" + signature);
    62. if (METHOD_NAME_POST.equals(httpMethod)) {
    63. return signature;
    64. }
    65. return URLEncoder.encode(signature, ALGORITHM);
    66. }
    67. /**
    68. * 计算签名中间产物 StringToSign。
    69. * @param httpMethod HTTP 请求的 Method。
    70. * @param parameter 原始请求串中参数名、参数值对的映射。
    71. * @return 签名中间产物 StringToSign。
    72. */
    73. public static String generateStringToSign(String httpMethod, Map<String, String> parameter)
    74. throws IOException {
    75. TreeMap<String, String> sortParameter = new TreeMap<String, String>(parameter);
    76. String canonicalizedQueryString = UrlUtil.canonicalizeQueryString(sortParameter, true);
    77. if (httpMethod == null || httpMethod.isEmpty()) {
    78. throw new RuntimeException("httpMethod can not be empty");
    79. }
    80. StringBuilder stringToSign = new StringBuilder();
    81. stringToSign.append(httpMethod).append(SEPARATOR);
    82. stringToSign.append(percentEncode("/")).append(SEPARATOR);
    83. stringToSign.append(percentEncode(canonicalizedQueryString));
    84. return stringToSign.toString();
    85. }
    86. /**
    87. * 对原文进行百分号编码处理。
    88. * @param text 要处理的原文。
    89. * @return 处理后的百分号编码。
    90. */
    91. public static String percentEncode(String text) {
    92. try {
    93. return text == null ? null
    94. : URLEncoder.encode(text, CHARSET_UTF8)
    95. .replace("+", "%20")
    96. .replace("*", "%2A")
    97. .replace("%7E", "~");
    98. } catch (Exception e) {
    99. System.out.println("Percentage encoding error:" + e.getMessage());
    100. }
    101. return "";
    102. }
    103. /**
    104. * HMAC-SHA1 键控散列。
    105. * @param secret HMAC-SHA1 使用的 Secret。
    106. * @param baseString 原文。
    107. * @return 散列值。
    108. */
    109. public static byte[] hmacSHA1Signature(String secret, String baseString)
    110. throws Exception {
    111. if (secret == null || secret.isEmpty()) {
    112. throw new IOException("secret can not be empty");
    113. }
    114. if (baseString == null || baseString.isEmpty()) {
    115. return null;
    116. }
    117. Mac mac = Mac.getInstance("HmacSHA1");
    118. SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(CHARSET_UTF8), ALGORITHM);
    119. mac.init(keySpec);
    120. return mac.doFinal(baseString.getBytes(CHARSET_UTF8));
    121. }
    122. /**
    123. * Base 64 编码。
    124. * @param bytes 原文。
    125. * @return Base 64 编码。
    126. */
    127. public static String newStringByBase64(byte[] bytes)
    128. throws UnsupportedEncodingException {
    129. if (bytes == null || bytes.length == 0) {
    130. return null;
    131. }
    132. return new String(Base64.encodeBase64(bytes, false), CHARSET_UTF8);
    133. }
    134. }
  4. DemoApplication.java

    1. package aliyun.signature;
    2. import java.io.UnsupportedEncodingException;
    3. import java.net.URLEncoder;
    4. import java.util.HashMap;
    5. import java.util.Map;
    6. /**
    7. * API 签名 Demo 主程序。
    8. *
    9. * @author Alibaba Cloud
    10. * @date 2019/01/20
    11. */
    12. public class DemoApplication {
    13. /**
    14. * 1. 需求修改 Config.java 中的 Access Key 信息。
    15. * 2. 建议使用方法二,所有参数都需要一一填写。
    16. * 3. "最终 Signature"才是你需要的签名最终结果。
    17. * @param args ...
    18. */
    19. public static void main(String[] args) throws UnsupportedEncodingException {
    20. // 方法一。
    21. System.out.println("方法一:");
    22. String str = "GET&%2F&AccessKeyId%3D" + Config.ACCESS_KEY_ID
    23. + "%26Action%3DGetGateway%26Format%3DJSON%26GwEui%3D"
    24. + "0000000000000000%26RegionId%3Dcn-shanghai%26Signa"
    25. + "tureMethod%3DHMAC-SHA1%26SignatureNonce%3D1521552"
    26. + "8852396%26SignatureVersion%3D1.0%26Timestamp%3D20"
    27. + "19-01-20T12%253A00%253A00Z%26Version%3D2019-01-20";
    28. byte[] signBytes;
    29. try {
    30. signBytes = SignatureUtils.hmacSHA1Signature(Config.ACCESS_KEY_SECRET + "&", str.toString());
    31. String signature = SignatureUtils.newStringByBase64(signBytes);
    32. System.out.println("signString---" + str);
    33. System.out.println("signature----" + signature);
    34. System.out.println("最终signature:" + URLEncoder.encode(signature, Config.CHARSET_UTF8));
    35. } catch (Exception e) {
    36. e.printStackTrace();
    37. }
    38. System.out.println();
    39. // 方法二。
    40. System.out.println("方法二:");
    41. Map<String, String> map = new HashMap<String, String>();
    42. // 公共参数。
    43. map.put("Format", "JSON");
    44. map.put("Version", "2019-01-20");
    45. map.put("AccessKeyId", Config.ACCESS_KEY_ID);
    46. map.put("SignatureMethod", "HMAC-SHA1");
    47. map.put("Timestamp", "2019-01-20T12:00:00Z");
    48. map.put("SignatureVersion", "1.0");
    49. map.put("SignatureNonce", "15215528852396");
    50. map.put("RegionId", "cn-shanghai");
    51. map.put("Action", "GetGateway");
    52. // 请求参数。
    53. map.put("GwEui", "0000000000000000");
    54. try {
    55. String signature = SignatureUtils.generate("GET", map, Config.ACCESS_KEY_SECRET);
    56. System.out.println("最终signature:" + signature);
    57. } catch (Exception e) {
    58. e.printStackTrace();
    59. }
    60. System.out.println();
    61. }
    62. }