Request signatures
ApsaraMQ for RocketMQ authenticates every HTTP API request by verifying a signature in the Authorization header. The following sections explain how to build a valid signature and attach it to your requests.
How authentication works
ApsaraMQ for RocketMQ uses symmetric encryption to verify the identity of each request sender:
You build a signature from the request content, using your AccessKey secret as the signing key.
You attach the signature to the
Authorizationheader of the HTTP request.The server computes the expected signature using the same algorithm and your AccessKey secret.
If the two signatures match, the request is authenticated. Otherwise, the server rejects the request with HTTP 403.
Prerequisites
Before you begin, make sure that you have:
An AccessKey pair (AccessKey ID and AccessKey secret) -- see Create an AccessKey pair
Apsara Stack issues an AccessKey pair to each user. You can apply for and manage AccessKey pairs in the Apsara Uni-manager Management Console. An AccessKey pair consists of two parts:
| Component | Purpose |
|---|---|
| AccessKey ID | Identifies who is making the request |
| AccessKey secret | Signs the request to prove your identity. Keep this value strictly confidential |
Authorization header format
Set the Authorization header in the following format:
MQ <AccessKey ID>:<Signature>Example with placeholder values:
MQ LTAI5tExampleKey:dGhpcyBpcyBhbiBleGFtcGxlIHNpZw==Build the signature
Building a valid signature involves three steps: assembling the string-to-sign, computing the HMAC-SHA1 hash, and Base64-encoding the result.
Step 1: Assemble the string-to-sign
Concatenate the following fields, separated by newline characters (\n), into a single UTF-8 string:
HTTP_METHOD + "\n"
+ "\n"
+ CONTENT-TYPE + "\n"
+ DATE + "\n"
+ "x-mq-version:" + MQVersion + "\n"
+ CanonicalizedResourceThe following table describes each field:
| Field | Description | Value or format |
|---|---|---|
| HTTP_METHOD | The HTTP method, in uppercase | PUT, GET, POST, or DELETE |
| CONTENT-TYPE | The media type of the request body | text/xml; charset=utf-8 |
| DATE | The request timestamp in RFC 2616 format (UTC) | Example: Thu, 07 Mar 2012 18:49:58 GMT |
| MQVersion | The ApsaraMQ for RocketMQ API version | 2015-06-06 |
| CanonicalizedResource | The URI of the resource being accessed, including query parameters | Example: /topics/abc/messages?consumer=GID_abc |
The second line in the string-to-sign is intentionally empty. Include the newline character but no content.
Example string-to-sign for a consume request:
POST
text/xml; charset=utf-8
Thu, 07 Mar 2012 18:49:58 GMT
x-mq-version:2015-06-06
/topics/abc/messages?consumer=GID_abcStep 2: Compute the HMAC-SHA1 hash
Apply the HMAC-SHA1 algorithm (RFC 2104) to the string-to-sign, using your AccessKey secret as the signing key.
Step 3: Base64-encode the result
Base64-encode the binary HMAC-SHA1 output to produce the final signature string.
The complete formula:
Signature = base64(hmac-sha1(AccessKeySecret, StringToSign))Function reference
| Function | Description |
|---|---|
hmac-sha1(key, message) | Computes an HMAC digest using the SHA-1 hash algorithm, as defined in RFC 2104. The key is your AccessKey secret |
base64(data) | Encodes binary data into a Base64 string |
Complete example
This example signs a message consumption request with the following parameters:
| Parameter | Value |
|---|---|
| HTTP method | POST |
| Content-Type | text/xml; charset=utf-8 |
| Date | Thu, 07 Mar 2012 18:49:58 GMT |
| API version | 2015-06-06 |
| Resource URI | /topics/abc/messages?consumer=GID_abc |
| AccessKey ID | LTAI5tExampleKey |
| AccessKey secret | xXxExampleSecretxXx |
Step 1 -- Assemble the string-to-sign:
POST
text/xml; charset=utf-8
Thu, 07 Mar 2012 18:49:58 GMT
x-mq-version:2015-06-06
/topics/abc/messages?consumer=GID_abcThe blank line after POST is the intentionally empty second line.
Step 2 and 3 -- Compute HMAC-SHA1 and Base64-encode:
Signature = base64(hmac-sha1("xXxExampleSecretxXx", StringToSign))Resulting Authorization header:
Authorization: MQ LTAI5tExampleKey:<computed-signature>Replace <computed-signature> with the Base64-encoded HMAC-SHA1 output.
Code examples
The following examples show how to generate the signature in common programming languages. Each example builds the string-to-sign, computes the HMAC-SHA1 hash, and Base64-encodes the result.
Python
import hmac
import hashlib
import base64
import os
from email.utils import formatdate
# Obtain credentials from environment variables.
access_key_id = os.environ["MQ_ACCESS_KEY_ID"]
access_key_secret = os.environ["MQ_ACCESS_KEY_SECRET"]
# Request parameters
http_method = "POST"
content_type = "text/xml; charset=utf-8"
date = formatdate(usegmt=True) # RFC 2616 format, e.g. "Thu, 07 Mar 2012 18:49:58 GMT"
mq_version = "2015-06-06"
canonicalized_resource = "/topics/abc/messages?consumer=GID_abc"
# Step 1: Assemble the string-to-sign.
string_to_sign = (
http_method + "\n"
+ "\n"
+ content_type + "\n"
+ date + "\n"
+ "x-mq-version:" + mq_version + "\n"
+ canonicalized_resource
)
# Step 2-3: Compute HMAC-SHA1 and Base64-encode.
signature = base64.b64encode(
hmac.new(
access_key_secret.encode("utf-8"),
string_to_sign.encode("utf-8"),
hashlib.sha1,
).digest()
).decode("utf-8")
# Build the Authorization header.
authorization = f"MQ {access_key_id}:{signature}"
print(authorization)Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.Locale;
public class MqSignature {
public static void main(String[] args) throws Exception {
// Obtain credentials from environment variables.
String accessKeyId = System.getenv("MQ_ACCESS_KEY_ID");
String accessKeySecret = System.getenv("MQ_ACCESS_KEY_SECRET");
// Request parameters
String httpMethod = "POST";
String contentType = "text/xml; charset=utf-8";
String date = DateTimeFormatter
.ofPattern("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US)
.format(ZonedDateTime.now(ZoneOffset.UTC));
String mqVersion = "2015-06-06";
String canonicalizedResource = "/topics/abc/messages?consumer=GID_abc";
// Step 1: Assemble the string-to-sign.
String stringToSign = httpMethod + "\n"
+ "\n"
+ contentType + "\n"
+ date + "\n"
+ "x-mq-version:" + mqVersion + "\n"
+ canonicalizedResource;
// Step 2-3: Compute HMAC-SHA1 and Base64-encode.
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(
accessKeySecret.getBytes(StandardCharsets.UTF_8), "HmacSHA1"));
byte[] rawSignature = mac.doFinal(
stringToSign.getBytes(StandardCharsets.UTF_8));
String signature = Base64.getEncoder().encodeToString(rawSignature);
// Build the Authorization header.
String authorization = "MQ " + accessKeyId + ":" + signature;
System.out.println(authorization);
}
}Go
package main
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"os"
"time"
)
func main() {
// Obtain credentials from environment variables.
accessKeyID := os.Getenv("MQ_ACCESS_KEY_ID")
accessKeySecret := os.Getenv("MQ_ACCESS_KEY_SECRET")
// Request parameters
httpMethod := "POST"
contentType := "text/xml; charset=utf-8"
date := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT")
mqVersion := "2015-06-06"
canonicalizedResource := "/topics/abc/messages?consumer=GID_abc"
// Step 1: Assemble the string-to-sign.
stringToSign := httpMethod + "\n" +
"\n" +
contentType + "\n" +
date + "\n" +
"x-mq-version:" + mqVersion + "\n" +
canonicalizedResource
// Step 2-3: Compute HMAC-SHA1 and Base64-encode.
mac := hmac.New(sha1.New, []byte(accessKeySecret))
mac.Write([]byte(stringToSign))
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
// Build the Authorization header.
authorization := fmt.Sprintf("MQ %s:%s", accessKeyID, signature)
fmt.Println(authorization)
}Node.js
const crypto = require("crypto");
// Obtain credentials from environment variables.
const accessKeyId = process.env.MQ_ACCESS_KEY_ID;
const accessKeySecret = process.env.MQ_ACCESS_KEY_SECRET;
// Request parameters
const httpMethod = "POST";
const contentType = "text/xml; charset=utf-8";
const date = new Date().toUTCString(); // RFC 2616 format
const mqVersion = "2015-06-06";
const canonicalizedResource = "/topics/abc/messages?consumer=GID_abc";
// Step 1: Assemble the string-to-sign.
const stringToSign = [
httpMethod,
"",
contentType,
date,
`x-mq-version:${mqVersion}`,
canonicalizedResource,
].join("\n");
// Step 2-3: Compute HMAC-SHA1 and Base64-encode.
const signature = crypto
.createHmac("sha1", accessKeySecret)
.update(stringToSign, "utf-8")
.digest("base64");
// Build the Authorization header.
const authorization = `MQ ${accessKeyId}:${signature}`;
console.log(authorization);Troubleshooting
The following table lists common signature errors and their solutions:
| Error | Cause | Solution |
|---|---|---|
| HTTP 403: Signature mismatch | The string-to-sign does not match the server's expectation | Print your string-to-sign and verify each field. Check that the empty second line is present and that no extra whitespace is appended |
| HTTP 403: Date offset too large | The DATE header value differs too much from the server clock | Synchronize your system clock with an NTP server and use UTC |
| HTTP 403: Invalid AccessKey ID | The AccessKey ID does not exist or is disabled | Verify that your AccessKey ID is correct and active in the console |
| Signature differs across runs | The DATE header changes per request, producing a different signature each time | This is expected behavior. Generate a new signature for each request |
| Encoding errors | The string-to-sign is not encoded as UTF-8 | Make sure all string operations use UTF-8 encoding |
Constraints
The string-to-sign must use UTF-8 encoding.
The
DATEheader value must be in UTC. Use the RFC 2616 date format:Day, DD Mon YYYY HH:MM:SS GMT.The
Authorizationheader must be included in every HTTP request.