本文介紹使用輕量訊息佇列(原 MNS)發送HTTP請求調用API時的請求結構、公用參數、返回結果及簽名認證方式。
請求結構
服務地址
輕量訊息佇列(原 MNS)支援多個地區,每個地區分別提供了公網訪問地址、內網訪問地址。更多資訊,請參見功能開服和存取點。
要求方法
輕量訊息佇列(原 MNS)支援通過HTTP協議進行請求通訊。使用HTTP的PUT、POST、GET、DELETE等HTTP Method發送不同的請求。
發送的請求需要帶上正確的請求參數、要求標頭和請求本文,請求及返回結果都使用UTF-8字元集進行編碼。
公用參數
公用請求參數
參數 | 是否必選 | 說明 |
Authorization | 是 | 驗證字串。更多資訊,請參見請求籤名機制。 |
Content-Length | 是 | HTTP訊息體的長度。 |
Content-Type | 是 | 請求內容的MIME類型,目前請求僅支援text或xml格式。 |
Content-MD5 | 否 | HTTP訊息體的MD5值。更多資訊,請參見The Content-MD5 Header Field。 |
Date | 是 | 請求的構造時間。目前只支援GMT格式,如果和SMQ的伺服器時間前後差異超過15分鐘將返回本次請求非法。 |
Host | 針對HTTP/1.1為必選,針對HTTP/1.0為可選。 | 從阿里雲官網擷取AccountId,從API文檔中擷取各地區SMQ訪問地址,格式如下: |
x-mns-version | 是 | 調用SMQ介面的版本號碼。目前的版本為2015-06-06。 |
x-mns-date | 否 | Date替代欄位。用於解決部分瀏覽器上使用者程式無法設定HTTP請求Date欄位的情境。 |
公用返回參數
參數 | 說明 |
Content-Length | HTTP訊息體返回的長度。 |
Connection | HTTP串連狀態。 |
Date | 響應的返回時間,目前只支援GMT格式。 |
Server | 請求響應的SMQ伺服器名。 |
x-mns-request-id | 此次Request操作的編號。 |
x-mns-version | SMQ介面的版本編號,目前的版本是2015-06-06。 |
返回結果
調用API服務後返回資料採用統一格式。返回的HTTP狀態代碼為2xx,說明調用成功;返回的HTTP狀態代碼為4xx或5xx,說明調用失敗。調用成功返回的資料格式為XML格式。
XML返回結果包括請求是否成功資訊和具體的業務資料。樣本如下:
<?xml version="1.0" encoding="utf-8"?>
<!--結果的根結點-->
<根節點 xmlns="http://mns.aliyuncs.com/doc/v1/">
<!--返回的子節點-->
</根節點>
調用介面出錯後,將不會返回結果資料,HTTP請求返回一個4xx或5xx的HTTP狀態代碼。返回的訊息體中是具體的錯誤碼及錯誤資訊。另外還包含一個全域唯一的請求ID:RequestId和一個您本次請求訪問的網站ID:HostId。
具體的錯誤資訊。請參見錯誤碼。
請求籤名機制
簽名組成
輕量訊息佇列(原 MNS)服務會對每個訪問的請求進行驗證,每個請求向輕量訊息佇列(原 MNS)提交時,都需要在該請求的Header中包含簽名(Authorization)。
輕量訊息佇列(原 MNS)通過使用AccessKeyId和AccessKeySecret進行對稱式加密的方法來驗證請求的寄件者身份。如果計算結果和提供的驗證碼一致,那麼該請求有效;如果計算結果和提供的驗證碼不一致,那麼輕量訊息佇列(原 MNS)將拒絕處理這次請求,並返回HTTP狀態代碼403。
您需要在HTTP請求中增加Authorization的Head來包含簽名資訊,表明這個訊息已被授權。格式為: Authorization: MNS AccessKeyId:Signature
。
您可以使用阿里雲帳號通過AccessKey管理頁面申請和管理AccessKeyID和AccessKeySecret。AccessKeyId用於標識訪問者的身份,AccessKeySecret是用於加密簽名字串和伺服器端驗證簽名字串的密鑰,這兩個參數必須嚴格保密。
計算方法
Signature = base64(hmac-sha1(HTTP_METHOD + "\n"
+ CONTENT-MD5 + "\n"
+ CONTENT-TYPE + "\n"
+ DATE + "\n"
+ CanonicalizedMNSHeaders
+ CanonicalizedResource))
參數 | 描述 |
HTTP_METHOD | 大寫的HTTP方法。例如:PUT、GET、POST、DELETE。 |
Content-Md5 | 請求內容資料的MD5值,如果請求的Header中沒有傳Content-MD5,則此處置空。 |
CONTENT-TYPE | 請求內容的類型。 |
DATE | 本次操作的時間。
|
CanonicalizedMNSHeaders | HTTP中的
|
CanonicalizedResource | HTTP所請求資源的URI。例如 |
用來簽名的字串為UTF-8格式。
簽名的方法用RFC 2104中定義的HMAC-SHA1方法,其中Key為AccessKeySecret。
content-type和content-md5在請求中不是必須的,參數沒有指定可以使用
''
來代替。
簽名樣本
請求樣本如下:
PUT /queues/$queueName?metaOverride=true HTTP/1.1
Host: $AccountId.mns.cn-hangzhou.aliyuncs.com
Date: Wed, 08 Mar 2012 12:00:00 GMT
Authorization: MNS 15B4D3461F177624****:xQE0diMbL****f3YB+FIEXAMPLE=
<?xml version="1.0" encoding="UTF-8" ?>
<Queue xmlns="http://mns.aliyuncs.com/doc/v1/">
<VisibilityTimeout >60</VisibilityTimeout>
<MaximumMessageSize>1024</MaximumMessageSize>
<MessageRetentionPeriod>120</MessageRetentionPeriod>
<DelaySeconds>30</DelaySeconds>
</Queue>
返回樣本如下:
樣本一
如果傳入的AccessKeyId不存在或disabled,返回403 Forbidden。
Content-Type: text/xml
Content-Length: 314
Date: Wed, 18Mar 2012 08:04:06 GMT
x-mns-request-id: 512B2A634403E52B1956****
<?xml version="1.0" encoding="utf-8"?>
<Error xmlns="http://mns.aliyuncs.com/doc/v1/">
<Code>AccessIDAuthError</Code>
<Message>
AccessID authentication fail, please check your AccessID and retry.
</Message>
<RequestId>512B2A634403E52B1956****</RequestId>
<HostId>mns.cn-hangzhou.aliyuncs.com</HostId>
</Error>
樣本二
如果簽名驗證的時候,Header中沒有傳入Date或者格式不正確,返回403 Forbidden。
Content-Type: text/xml
Content-Length: 274
Date: Wed, 18Mar 2012 08:04:06 GMT
x-mns-request-id: 512B2A634403E52B1956****
<?xml version="1.0" encoding="UTF-8"?>
<Error xmlns="http://mns.aliyuncs.com/doc/v1/">
<Code>InvalidArgument</Code>
<Message>Date Header is invalid or missing.</Message>
<RequestId>7E1A5CF258F535884403****</RequestId>
<HostId>mns.cn-hangzhou.aliyuncs.com</HostId>
</Error>
樣本三
傳入請求的時間在輕量訊息佇列(原 MNS)伺服器目前時間之後的15分鐘以內,否則返回408逾時。
Content-Type: text/xml
Content-Length: 283
Date: Wed, 11 May 2011 09:01:51 GMT
x-mns-request-id: 512B2A634403E52B1956****
<?xml version="1.0" encoding="UTF-8"?>
<Error xmlns="http://mns.aliyuncs.com/doc/v1/">
<Code>TimeExpired</Code>
<Message>
The http request you sent is expired.
</Message>
<RequestId>512B2A634403E52B1956****</RequestId>
<HostId>mns.cn-hangzhou.aliyuncs.com</HostId>
</Error>
Endpoint簽名認證
在輕量訊息佇列(原 MNS)推送要求標頭中,Authorization欄位的值是輕量訊息佇列(原 MNS)根據待簽名字串,用SHA1-RSA簽名演算法產生的簽名。Endpoint可以使用公開金鑰對簽名進行驗證,具體的驗證方法如下:
步驟一:擷取X509認證
在輕量訊息佇列(原 MNS)發送給Endpoint的HTTP要求標頭中,x-mns-signing-cert-url指定了簽署憑證的地址,您需要通過Base64解碼,擷取該簽名檔案URL地址,再從中提取出簽名的公開金鑰。
僅當簽署憑證的地址首碼為https://mnstest.oss-cn-hangzhou.aliyuncs.com/
時,該認證合法。否則,該認證不合法。更多資訊,請參見如何確認輕量訊息佇列(原 MNS)推送請求中的密鑰憑證地址是阿里雲官方的。
步驟二:計算待簽名字串
VERB + "\n"
+ CONTENT-MD5 + "\n"
+ CONTENT-TYPE + "\n"
+ DATE + "\n"
+ CanonicalizedMNSHeaders
+ CanonicalizedResource
參數 | 樣本 |
VERB | HTTP的方法 |
CONTENT-MD5 | 請求內容資料的MD5值。 |
CONTENT-TYPE | 請求內容的類型,對應的值為全小寫。 |
DATE | 此次操作的時間,不可為空,目前只支援GMT格式。 |
CanonicalizedMNSHeaders | HTTP要求標頭中的x-mns-開頭的欄位組合。 |
CanonicalizedResource | 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簽名欄位進行Base64解碼後,使用從步驟一中提取的公開金鑰對其進行解密。
步驟四:認證
比較步驟二產生的待簽名字串與步驟三解密的結果是否一致。如果一致,說明請求來自輕量訊息佇列(原 MNS),訪問合法。
CanonicalizedMNSHeaders在簽名驗證前需要符合以下規範:
Head的名字需要變成小寫。
Head自小到大排序。
分割Headname和value的冒號前後不能有空格。
每個Head之後都有一個\n,如果沒有以x-mns-開頭的Head,則在簽名時CanonicalizedMNSHeaders就設定為空白。
其他認證說明:
用來簽名的字串為UTF-8格式。
簽名的方法用RFC 3447中定義的sha1WithRSAEncryption方法。
Base64是指使用Base64演算法轉碼文本。
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);
//根據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);
//認證。
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);
}
}