All Products
Search
Document Center

Simple Message Queue (formerly MNS):Verify HTTP push message signatures

Last Updated:Mar 11, 2026

When Simple Message Queue (formerly MNS) pushes a topic message to your HTTP endpoint, the request includes an RSA-SHA1 signature in the Authorization header. Verify this signature to confirm that the message originates from SMQ and has not been tampered with.

Security best practices

Review these requirements before you implement signature verification:

  • Validate the certificate URL. The signing certificate URL must start with https://mnstest.oss-cn-hangzhou.aliyuncs.com/. Reject any certificate from a different prefix. For details, see How do I check whether the URL of a public key certificate is provided by Alibaba Cloud?

  • Check the timestamp. The Date header has a 15-minute validity window. SMQ returns error code 400 for expired requests. Reject messages with timestamps older than 15 minutes to protect your endpoint from replay attacks. For more information, see Error codes.

How signature verification works

Signature verification consists of four steps:

  1. Retrieve the X.509 signing certificate and extract the public key.

  2. Construct a string-to-sign from the HTTP request.

  3. Base64-decode the Authorization header and decrypt it with the public key.

  4. Compare the decrypted value with the string-to-sign. A match confirms the request is authentic.

Sample incoming request

SMQ sends an HTTP POST request to your endpoint with headers similar to the following:

POST /notifications HTTP/1.1
Content-Type: text/xml;charset=utf-8
Content-MD5: ZDgxNjY5ZjFlMDQ5MGM0YWMwMWE5ODlmZDVlYmQxYjI=
Date: Wed, 25 May 2016 10:46:14 GMT
Authorization: Mko2Azg9fhCw8qR6G7AeAFMyzjO9qn7LDA5/t9E+6X5XURXTqBUuhpK+K55UNhrnlE2UdDkRrwDxsaDP5ajQ****
x-mns-request-id: 57458276F0E3D56D7C00****
x-mns-signing-cert-url: aHR0cDovL21uc3Rlc3Qub3NzLWNuLWhhbmd6aG91LmFsaXl1bmNzLmNvbS94NTA5X3B1YmxpY19jZXJ0aWZpY2F0ZS5w****
x-mns-version: 2015-06-06

The x-mns-signing-cert-url value is Base64-encoded. After decoding, it yields the certificate download URL.

Step 1: Retrieve the X.509 certificate

Extract the x-mns-signing-cert-url header from the incoming request and Base64-decode its value to get the certificate URL. Download the certificate from that URL and extract the public key.

Important

Only accept certificates from URLs that start with https://mnstest.oss-cn-hangzhou.aliyuncs.com/. Reject requests with any other certificate URL prefix.

Step 2: Construct the string-to-sign

Build the string-to-sign by concatenating request components in this format:

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

Parameters

ParameterDescription
HttpMethodHTTP method in uppercase, such as POST, GET, PUT, or DELETE.
CONTENT-MD5MD5 hash of the request body. Leave blank if the Content-MD5 header is absent.
CONTENT-TYPEContent type of the request body. Leave blank if the Content-Type header is absent.
DATERequest timestamp in GMT format, such as Thu, 07 Mar 2012 18:49:58 GMT. If the request uses x-mns-date instead of Date, use the x-mns-date value. This parameter is required. SMQ must receive the request within 15 minutes after it is sent. Otherwise, SMQ returns error code 400. For more information, see Error codes.
CanonicalizedMNSHeadersAll x-mns-* headers, sorted alphabetically by lowercase name, concatenated in key:value\n format. See Construct CanonicalizedMNSHeaders.
CanonicalizedResourceURI path and query string, excluding the domain and port. For example, http://123.123.XX.XX:8080/api/test?code=200 yields /api/test?code=200.
Important

If gateways or other transit gateways are configured in your system, use the original URI from the HTTP request, not the URI modified by gateways.

Construct CanonicalizedMNSHeaders

Sort all x-mns-* headers alphabetically by lowercase name and concatenate them:

// Get request headers
Map<String, String> httpHeaders = request.getHeaders();
// Sort headers and convert names to lowercase
sortHeadersKeyAndToLowerCase(httpHeaders);
// Concatenate x-mns-* headers
Set<String> keySet = httpHeaders.keySet();
for (String key : keySet) {
    if (key.startsWith("x-mns-")) {
        CanonicalizedMNSHeaders.append(key).append(":")
            .append(httpHeaders.get(key)).append("\n");
    }
}

Example string-to-sign

Based on the sample request above, the resulting string-to-sign is:

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

Step 3: Decrypt the Authorization header

Base64-decode the Authorization header value, then decrypt the decoded bytes with the RSA public key from Step 1.

Step 4: Compare and verify

Compare the string-to-sign from Step 2 with the decrypted value from Step 3:

  • Match: The request is authentic. Process the message.

  • No match: The request may be forged. Reject it.

Java sample code

The following example combines all four steps. The authenticate method retrieves the signing certificate, constructs the string-to-sign, and verifies the RSA-SHA1 signature.

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 {
            // Step 1: Retrieve the signing certificate URL from the request header
            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;
            }
            // Base64-decode the header value to get the actual certificate URL
            cert = new String(Base64.decodeBase64(cert));
            System.out.println("x-mns-signing-cert-url:\t" + cert);

            // Download the certificate and extract the public key
            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();

            // Step 2: Construct the string-to-sign
            String str2sign = getSignStr(method, uri, headers);
            System.out.println("String2Sign:\t" + str2sign);

            // Step 3: Base64-decode the Authorization header
            String signature = headers.get("Authorization");
            byte[] decodedSign = Base64.decodeBase64(signature);

            // Step 4: Verify the RSA-SHA1 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);
    }
}