すべてのプロダクト
Search
ドキュメントセンター

Simple Message Queue (formerly MNS):HTTP サーバーでの署名の検証

最終更新日:Mar 26, 2025

Simple Message Queue (旧称 MNS) からエンドポイントへのメッセージプッシュのリクエストでは、Authorization ヘッダーの署名は、文字列署名に RSA-SHA1 アルゴリズムを実装することで生成されます。このトピックでは、公開鍵を使用して HTTP サーバーで署名を検証する方法について説明します。

手順 1:X509 証明書を取得する

SMQ からエンドポイントに送信される HTTP リクエストでは、x-mns-signing-cert-url ヘッダーは、署名証明書の URL を示す文字列を指定します。署名証明書の URL を取得するには、文字列で Base64 デコードを実行し、証明書から公開鍵を取得する必要があります。

説明

署名証明書の URL プレフィックスが https://mnstest.oss-cn-hangzhou.aliyuncs.com/ の場合にのみ、署名証明書は有効です。それ以外の場合、証明書は無効です。詳細については、「公開鍵証明書の URL が Alibaba Cloud によって提供されているかどうかを確認するにはどうすればよいですか?」をご参照ください。

手順 2:文字列署名の値を計算する

次の擬似コードに基づいて、文字列署名を構築します。

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

次の表に、パラメーターを示します。

パラメーター

説明

HttpMethod

PUT、GET、POST、DELETE など、大文字の HTTP メソッド。

Content-MD5

リクエストボディの MD5 ハッシュ値。Content-MD5 ヘッダーが指定されていない場合は、このパラメーターを空のままにします。

CONTENT-TYPE

リクエストボディのタイプ。Content-Type ヘッダーが指定されていない場合は、このパラメーターを空のままにします。

DATE

リクエストが送信された時刻。

  • 例:Thu, 07 Mar 2012 18:49:58 GMT。Date ヘッダーの代わりに x-mns-date ヘッダーを使用する場合は、このパラメーターを x-mns-date ヘッダーの値に設定します。

  • このパラメーターは必須であり、値は GMT 形式である必要があります。

  • リクエストの送信後 15 分以内に SMQ がリクエストを受信しない場合、SMQ はリクエストが無効であると見なし、エラーコード 400 を返します。詳細については、「エラーコード」をご参照ください。

CanonicalizedMNSHeaders

x-mns- というプレフィックスが付いた HTTP ヘッダーの組み合わせ。このパラメーターの値は、次の要件を満たしている必要があります。

  • ヘッダー名は小文字である必要があります。

  • ヘッダーはアルファベット順にソートする必要があります。

  • ヘッダーが連結されます。

    // 元のリクエストヘッダー。
    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(Uniform Resource Identifier)。リクエストに応答する HTTP サーバーの IP アドレスからドメイン名とポート番号を削除することで、URI を取得できます。たとえば、エンドポイント http://123.123.XX.XX:8080/api/test?code=200 を使用する場合は、URI は /api/test?code=200 です。http://www.aliyun.com/mns/help の URI は /mns/help です。

重要

システムにゲートウェイまたは他の通過ゲートウェイが構成されているシナリオでは、HTTP リクエストによってリクエストされたリソースの URI が、SMQ によって検証される API リクエストの URI と同じでない場合は、HTTP リクエストによってリクエストされたリソースの 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       

手順 3:Authorization ヘッダーを復号化する

Authorization ヘッダーで Base64 デコードを実行した後、手順 1 で取得した公開鍵を使用して、デコードされた値を復号化できます。

手順 4:署名を検証する

手順 2 で生成された文字列署名を、手順 3 で生成された復号化された文字列と比較します。2 つの文字列が同じであれば、SMQ からのリクエストは有効です。それ以外の場合は、リクエストを拒否します。

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