主題消息:HTTP訂閱簽名
在輕量消息隊列(原 MNS)推送請求頭中,Authorization字段的值是輕量消息隊列(原 MNS)根據(jù)待簽名字符串,用SHA1-RSA簽名算法生成的簽名。本文介紹Endpoint如何使用公鑰對簽名進(jìn)行驗證。
步驟一:獲取X509證書
在輕量消息隊列(原 MNS)發(fā)送給Endpoint的HTTP請求頭中,x-mns-signing-cert-url指定了簽名證書的地址,您需要通過Base64解碼,獲取該簽名文件URL地址,再從中提取出簽名的公鑰。
僅當(dāng)簽名證書的地址前綴為https://mnstest.oss-cn-hangzhou.aliyuncs.com/
時,該證書合法。否則,該證書不合法。更多信息,請參見如何確認(rèn)輕量消息隊列(原 MNS)推送請求中的公鑰證書地址是阿里云官方的。
步驟二:構(gòu)造待簽名字符串(StringToSign)
按照以下偽代碼構(gòu)造待簽名的字符串(StringToSign)
StringToSign = HttpMethod + "\n"
+ CONTENT-MD5 + "\n"
+ CONTENT-TYPE + "\n"
+ DATE + "\n"
+ CanonicalizedMNSHeaders
+ CanonicalizedResource;
其中,各參數(shù)的含義如下:
參數(shù) | 描述 |
HttpMethod | 大寫的HTTP方法。例如:PUT、GET、POST、DELETE。 |
Content-Md5 | 請求內(nèi)容數(shù)據(jù)的MD5值,若無則為空。 |
CONTENT-TYPE | 請求內(nèi)容的類型,若無則為空。 |
DATE | 本次操作的時間。
|
CanonicalizedMNSHeaders | HTTP中的
|
CanonicalizedResource | HTTP所請求資源的URI,是指對應(yīng)訂閱配置的HTTP接收端地址去除域名端口的部分。例如配置的接收端地址是 |
待簽名字符串示例:
POST
ZDgxNjY5ZjFlMDQ5MGM0YWMwMWE5ODlmZDVlYmQxYjI=
text/xml;charset=utf-8
Wed, 25 May 2016 10:46:14 GMT
x-mns-request-id:57458276F0E3D56D7C00****
x-mns-signing-cert-url:aHR0cDovL21uc3Rlc3Qub3NzLWNuLWhhbmd6aG91LmFsaXl1bmNzLmNvbS94NTA5X3B1YmxpY19jZXJ0aWZpY2F0ZS5w****
x-mns-version:2015-06-06
/notifications
步驟三:Authorization解密
對Authorization簽名字段進(jìn)行Base64解碼后,使用從步驟一:獲取X509證書中提取的公鑰對其進(jìn)行解密。
步驟四:認(rèn)證
比較步驟二:構(gòu)造待簽名字符串(StringToSign)生成的待簽名字符串與步驟三:Authorization解密的結(jié)果是否一致。如果結(jié)果一致,說明請求來自輕量消息隊列(原 MNS),訪問合法;如果結(jié)果不一致,說明訪問請求非法,拒絕請求。
Java示例代碼
public class SignDemo {
private Boolean authenticate(String method, String uri, Map<String, String> headers) {
try {
//獲取證書的URL。
if (!headers.containsKey("x-mns-signing-cert-url")) {
System.out.println("x-mns-signing-cert-url Header not found");
return false;
}
String cert = headers.get("x-mns-signing-cert-url");
if (cert.isEmpty()) {
System.out.println("x-mns-signing-cert-url empty");
return false;
}
cert = new String(Base64.decodeBase64(cert));
System.out.println("x-mns-signing-cert-url:\t" + cert);
//根據(jù)URL獲取證書,并從證書中獲取公鑰。
URL url = new URL(cert);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
DataInputStream in = new DataInputStream(conn.getInputStream());
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate c = cf.generateCertificate(in);
PublicKey pk = c.getPublicKey();
//獲取待簽名字符串。
String str2sign = getSignStr(method, uri, headers);
System.out.println("String2Sign:\t" + str2sign);
//對Authorization字段做Base64解碼。
String signature = headers.get("Authorization");
byte[] decodedSign = Base64.decodeBase64(signature);
//認(rèn)證。
java.security.Signature signetcheck = java.security.Signature.getInstance("SHA1withRSA");
signetcheck.initVerify(pk);
signetcheck.update(str2sign.getBytes());
Boolean res = signetcheck.verify(decodedSign);
return res;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
private String getSignStr(String method, String uri, Map<String, String> headers) {
StringBuilder sb = new StringBuilder();
sb.append(method);
sb.append("\n");
sb.append(safeGetHeader(headers, "Content-md5"));
sb.append("\n");
sb.append(safeGetHeader(headers, "Content-Type"));
sb.append("\n");
sb.append(safeGetHeader(headers, "Date"));
sb.append("\n");
List<String> tmp = new ArrayList<String>();
for (Map.Entry<String, String> entry : headers.entrySet()) {
if (entry.getKey().startsWith("x-mns-")) {
tmp.add(entry.getKey() + ":" + entry.getValue());
}
}
Collections.sort(tmp);
for (String kv : tmp) {
sb.append(kv);
sb.append("\n");
}
sb.append(uri);
return sb.toString();
}
private String safeGetHeader(Map<String, String> headers, String name) {
if (headers.containsKey(name)) {
return headers.get(name);
} else {
return "";
}
}
public static void main(String[] args) {
SignDemo sd = new SignDemo();
Map<String, String> headers = new HashMap<String, String>();
headers.put("Authorization", "Mko2Azg9fhCw8qR6G7AeAFMyzjO9qn7LDA5/t9E+6X5XURXTqBUuhpK+K55UNhrnlE2UdDkRrwDxsaDP5ajQ****");
headers.put("Content-md5", "M2ViOTE2ZDEyOTlkODBjMjVkNzM4YjNhNWI3ZWQ1****");
headers.put("Content-Type", "text/xml;charset=utf-8");
headers.put("Date", "Tue, 23 Feb 2016 09:41:06 GMT");
headers.put("x-mns-request-id", "56CC2932F0E3D5BD5306****");
headers.put("x-mns-signing-cert-url", "aHR0cDovL21uc3Rlc3Qub3NzLWNuLWhhbmd6aG91LmFsaXl1bmNzLmNvbS94NTA5X3B1YmxpY19jZXJ0aWZpY2F0ZS5w****");
headers.put("x-mns-version", "2015-06-06");
Boolean res = sd.authenticate("POST", "/notifications", headers);
System.out.println("Authenticate result:" + res);
}
}