阅读294 返回首页    go 微信


Endpoint签名认证__调用方式_API使用手册_消息服务-阿里云

用户可以通过推送请求Header中的 x-mns-signing-cert-url 获取签名证书,并根据相应的方法来验证该请求是否由MNS系统发出,防止恶意请求对用户造成负面影响。

在 MNS 推送请求的 Header 中,Authorization 字段的值是MNS根据待签名字符串,用 SHA1-RSA 签名算法生成签名。Endpoint 可以使用公钥对签名进行验证,具体的验证方法如下:

第一步:获取X509证书

在 MNS 发送给 Endpoint 的 http 请求 Header 中,x-mns-signing-cert-url 指定了签名证书的地址( Base64 编码),用户需要通过 Base64 解码,获取该签名文件 URL 地址,再从中提取出签名的公钥。

第二步:计算待签名字符串

  1. VERB + "n"
  2. + CONTENT-MD5 + "n"
  3. + CONTENT-TYPE + "n"
  4. + DATE + "n"
  5. + CanonicalizedMNSHeaders
  6. + CanonicalizedResource
  • VERB 表示HTTP的Method
  • CONTENT-MD5 表示请求内容数据的MD5值
  • CONTENT-TYPE 表示请求内容的类型,对应的值为全小写
  • DATE 表示此次操作的时间,不能为空,目前只支持GMT格式
  • CanonicalizedMNSHeaders 表示 http 请求 Header 中的x-mns-开始的字段组合(见下文注意事项)
  • CanonicalizedResource表示 http 请求的相对地址,不能为空

待签名字符串示例(注:不应该出现空的行全小写,Content-Type值为全小写):

  1. POST
  2. ZDgxNjY5ZjFlMDQ5MGM0YWMwMWE5ODlmZDVlYmQxYjI=
  3. text/xml;charset=utf-8
  4. Wed, 25 May 2016 10:46:14 GMT
  5. x-mns-request-id:57458276F0E3D56D7C00054B
  6. x-mns-signing-cert-url:aHR0cDovL21uc3Rlc3Qub3NzLWNuLWhhbmd6aG91LmFsaXl1bmNzLmNvbS94NTA5X3B1YmxpY19jZXJ0aWZpY2F0ZS5wZW0=
  7. x-mns-version:2015-06-06
  8. /notifications

第三步:Authorization 解密

对Authorization 签名字段进行 Base64 Decode 解码后,使用从第一步从证书中提取的公钥对其进行解密;

第四步:认证

比较第二步生成的待签名字符串与第三步解密的结果是否一致。如果一致,则表明请求来自MNS,访问合法。

注意事项

CanonicalizedMNSHeaders(即x-mns-开头的head)在签名验证前需要符合以下规范:

  • head 的名字需要变成小写
  • head 自小到大排序
  • 分割 head name 和 value 的冒号前后不能有空格
  • 每个Head之后都有一个n,如果没有以x-mns-开头的head,则在签名时CanonicalizedMNSHeaders就设置为空

其他

  1. 用来签名的字符串为 UTF-8 格式;
  2. 签名的方法用 RFC 3447 (https://tools.ietf.org/html/rfc3447)中定义的 sha1WithRSAEncryption 方法;
  3. Base64 是指使用 base64 算法转码文本。

Java示例代码

  1. public class SignDemo {
  2. private Boolean authenticate(String method, String uri, Map<String, String> headers) {
  3. try {
  4. //获取证书的URL
  5. if (!headers.containsKey("x-mns-signing-cert-url")) {
  6. System.out.println("x-mns-signing-cert-url Header not found");
  7. return false;
  8. }
  9. String cert = headers.get("x-mns-signing-cert-url");
  10. if (cert.isEmpty()) {
  11. System.out.println("x-mns-signing-cert-url empty");
  12. return false;
  13. }
  14. cert = new String(Base64.decodeBase64(cert));
  15. System.out.println("x-mns-signing-cert-url:t" + cert);
  16. //根据URL获取证书,并从证书中获取公钥
  17. URL url = new URL(cert);
  18. HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  19. DataInputStream in = new DataInputStream(conn.getInputStream());
  20. CertificateFactory cf = CertificateFactory.getInstance("X.509");
  21. Certificate c = cf.generateCertificate(in);
  22. PublicKey pk = c.getPublicKey();
  23. //获取待签名字符串
  24. String str2sign = getSignStr(method, uri, headers);
  25. System.out.println("String2Sign:t" + str2sign);
  26. //对Authorization字段做Base64解码
  27. String signature = headers.get("Authorization");
  28. byte[] decodedSign = Base64.decodeBase64(signature);
  29. //认证
  30. java.security.Signature signetcheck = java.security.Signature.getInstance("SHA1withRSA");
  31. signetcheck.initVerify(pk);
  32. signetcheck.update(str2sign.getBytes());
  33. Boolean res = signetcheck.verify(decodedSign);
  34. return res;
  35. } catch (Exception e) {
  36. e.printStackTrace();
  37. return false;
  38. }
  39. }
  40. private String getSignStr(String method, String uri, Map<String, String> headers) {
  41. StringBuilder sb = new StringBuilder();
  42. sb.append(method);
  43. sb.append("n");
  44. sb.append(safeGetHeader(headers, "Content-md5"));
  45. sb.append("n");
  46. sb.append(safeGetHeader(headers, "Content-Type"));
  47. sb.append("n");
  48. sb.append(safeGetHeader(headers, "Date"));
  49. sb.append("n");
  50. List<String> tmp = new ArrayList<String>();
  51. for (Map.Entry<String, String> entry : headers.entrySet()) {
  52. if (entry.getKey().startsWith("x-mns-")) {
  53. tmp.add(entry.getKey() + ":" + entry.getValue());
  54. }
  55. }
  56. Collections.sort(tmp);
  57. for (String kv : tmp) {
  58. sb.append(kv);
  59. sb.append("n");
  60. }
  61. sb.append(uri);
  62. return sb.toString();
  63. }
  64. private String safeGetHeader(Map<String, String> headers, String name) {
  65. if (headers.containsKey(name)) {
  66. return headers.get(name);
  67. } else {
  68. return "";
  69. }
  70. }
  71. public static void main(String[] args) {
  72. SignDemo sd = new SignDemo();
  73. Map<String, String> headers = new HashMap<String, String>();
  74. headers.put("Authorization", "Mko2Azg9fhCw8qR6G7AeAFMyzjO9qn7LDA5/t9E+6X5XURXTqBUuhpK+K55UNhrnlE2UdDkRrwDxsaDP5ajQdg==");
  75. headers.put("Content-md5", "M2ViOTE2ZDEyOTlkODBjMjVkNzM4YjNhNWI3ZWQ1M2E=");
  76. headers.put("Content-Type", "text/xml;charset=utf-8");
  77. headers.put("Date", "Tue, 23 Feb 2016 09:41:06 GMT");
  78. headers.put("x-mns-request-id", "56CC2932F0E3D5BD530685CB");
  79. headers.put("x-mns-signing-cert-url", "aHR0cDovL21uc3Rlc3Qub3NzLWNuLWhhbmd6aG91LmFsaXl1bmNzLmNvbS94NTA5X3B1YmxpY19jZXJ0aWZpY2F0ZS5wZW0=");
  80. headers.put("x-mns-version", "2015-06-06");
  81. Boolean res = sd.authenticate("POST", "/notifications", headers);
  82. System.out.println("Authenticate result:" + res);
  83. }
  84. }

最后更新:2016-11-23 17:16:04

  上一篇:go 请求签名机制__调用方式_API使用手册_消息服务-阿里云
  下一篇:go RESTfulAPI概述__队列接口规范_API使用手册_消息服务-阿里云