全部產品
Search
文件中心

Simple Message Queue (formerly MNS):主題訊息:HTTP訂閱簽名

更新時間:Mar 27, 2025

輕量訊息佇列(原 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)推送請求中的密鑰憑證地址是阿里雲官方的

步驟二:計算待簽名字串

按照以下虛擬碼構造待簽名的字串(StringToSign)

StringToSign = HttpMethod + "\n" 
         + CONTENT-MD5 + "\n"     
         + CONTENT-TYPE + "\n" 
         + DATE + "\n" 
         + CanonicalizedMNSHeaders
         + CanonicalizedResource;

其中,各參數的含義如下:

參數

描述

HttpMethod

大寫的HTTP方法。例如:PUT、GET、POST、DELETE。

Content-Md5

請求內容資料的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-開頭的欄位組合。該欄位在簽名驗證前需要符合以下規範:

  • Header的key需要變成小寫(toLowerCase)。

  • Header自小到大排序。

  • 拼接虛擬碼。

    // 原始請求Header
    Map<String, String> httpHeaders = request.getHeaders();
    // 排序和小寫
    sortHeadersKeyAndToLowerCase(httpHeaders);
    // 拼接
    Set<String> keySet = httpHeaders.keySet();
    for (String key : keySet) {
        if (key.startsWith("x-mns-")) {
            CanonicalizedMNSHeaders.append(key).append(":")
                .append(httpHeaders.get(key)).append("\n");
        }
    }

CanonicalizedResource

HTTP所請求資源的URI,是指對應訂閱配置的HTTP接收端地址去除網域名稱連接埠的部分。例如配置的接收端地址是http://123.123.XX.XX:8080/api/test?code=200,則URI是/api/test?code=200;如果接收端地址是http://www.aliyun.com/mns/help,則URI是/mns/help

重要

如果您的系統中存在網關或其他中轉,可能會出現校正處的 URI和訂閱配置不一致的情況,此時需要以訂閱配置為準!

待簽名字串樣本:

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解碼後,使用從步驟一:擷取X509認證中提取的公開金鑰對其進行解密。

步驟四:認證

比較步驟二:計算待簽名字串產生的待簽名字串與步驟三:Authorization解密的結果是否一致。如果結果一致,說明請求來自輕量訊息佇列(原 MNS),訪問合法;如果結果不一致,說明訪問請求非法,拒絕請求。

Java範例程式碼

import org.apache.commons.codec.binary.Base64;

import java.io.DataInputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


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);
    }
}