全部產品
Search
文件中心

Simple Message Queue (formerly MNS):請求協議說明

更新時間:Sep 03, 2024

本文介紹使用輕量訊息佇列(原 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訪問地址,格式如下:$AccountId.mns.cn-hangzhou.aliyuncs.com

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

本次操作的時間。

  • 格式為:Thu, 07 Mar 2012 18:49:58 GMT。如果用x-mns-date替代DATE,則DATE不能填空,需用x-mns-date的值替換。

  • 此參數不可為空,目前只支援GMT格式。

  • 如果請求時間和輕量訊息佇列(原 MNS)伺服器時間相差超過15分鐘,輕量訊息佇列(原 MNS)會判定此請求不合法,返回錯誤碼400。更多錯誤資訊及錯誤碼,請參見錯誤碼

CanonicalizedMNSHeaders

HTTP中的x-mns-開頭的欄位組合。該欄位在簽名驗證前需要符合以下規範:

  • head的名字需要變成小寫。

  • head自小到大排序。

  • 分割head name和value的冒號前後不能有空格。

  • 每個Head之後都有一個\n,如果沒有以x-mns-開頭的head,則在簽名時CanonicalizedMNSHeaders就設定為空白。

CanonicalizedResource

HTTP所請求資源的URI。例如/queues/$queueName?metaOverride=true

說明
  • 用來簽名的字串為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);
    }
}