Saat Simple Message Queue (formerly MNS) mendorong pesan topik ke endpoint HTTP Anda, permintaan tersebut menyertakan signature RSA-SHA1 dalam header Authorization. Verifikasi signature ini untuk memastikan bahwa pesan berasal dari SMQ dan tidak dimodifikasi.
Praktik keamanan terbaik
Tinjau persyaratan berikut sebelum mengimplementasikan verifikasi signature:
Validasi URL sertifikat. URL sertifikat penandatangan harus diawali dengan
https://mnstest.oss-cn-hangzhou.aliyuncs.com/. Tolak sertifikat apa pun dari awalan yang berbeda. Untuk detailnya, lihat Bagaimana cara memeriksa apakah URL sertifikat kunci publik disediakan oleh Alibaba Cloud?Periksa timestamp. Header
Datememiliki jendela validitas 15 menit. SMQ mengembalikan kode kesalahan 400 untuk permintaan yang kedaluwarsa. Tolak pesan dengan timestamp lebih dari 15 menit lalu untuk melindungi endpoint Anda dari serangan replay. Untuk informasi selengkapnya, lihat Kode kesalahan.
Cara kerja verifikasi signature
Verifikasi signature terdiri dari empat langkah:
Ambil sertifikat penandatangan X.509 dan ekstrak kunci publik.
Buat string-to-sign dari permintaan HTTP.
Base64-decode header
Authorizationdan dekripsi nilainya dengan kunci publik.Bandingkan nilai hasil dekripsi dengan string-to-sign. Jika sesuai, permintaan tersebut autentik.
Contoh permintaan masuk
SMQ mengirim permintaan HTTP POST ke endpoint Anda dengan header seperti berikut:
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-06Nilai x-mns-signing-cert-url dienkripsi Base64. Setelah didekode, nilai tersebut menghasilkan URL unduhan sertifikat.
Langkah 1: Ambil sertifikat X.509
Ekstrak header x-mns-signing-cert-url dari permintaan masuk dan Base64-decode nilainya untuk mendapatkan URL sertifikat. Unduh sertifikat dari URL tersebut dan ekstrak kunci publiknya.
Hanya terima sertifikat dari URL yang diawali dengan https://mnstest.oss-cn-hangzhou.aliyuncs.com/. Tolak permintaan dengan awalan URL sertifikat lainnya.
Langkah 2: Buat string-to-sign
Buat string-to-sign dengan menggabungkan komponen permintaan dalam format berikut:
StringToSign = HttpMethod + "\n"
+ CONTENT-MD5 + "\n"
+ CONTENT-TYPE + "\n"
+ DATE + "\n"
+ CanonicalizedMNSHeaders
+ CanonicalizedResource;Parameter
| Parameter | Deskripsi |
|---|---|
| HttpMethod | Metode HTTP dalam huruf kapital, seperti POST, GET, PUT, atau DELETE. |
| CONTENT-MD5 | Hash MD5 dari badan permintaan. Biarkan kosong jika header Content-MD5 tidak ada. |
| CONTENT-TYPE | Jenis konten dari badan permintaan. Biarkan kosong jika header Content-Type tidak ada. |
| DATE | Timestamp permintaan dalam format GMT, seperti Thu, 07 Mar 2012 18:49:58 GMT. Jika permintaan menggunakan x-mns-date alih-alih Date, gunakan nilai x-mns-date. Parameter ini wajib diisi. SMQ harus menerima permintaan dalam waktu 15 menit setelah dikirim. Jika tidak, SMQ mengembalikan kode kesalahan 400. Untuk informasi selengkapnya, lihat Kode kesalahan. |
| CanonicalizedMNSHeaders | Semua header x-mns-*, diurutkan secara alfabetis berdasarkan nama huruf kecil, lalu digabungkan dalam format key:value\n. Lihat Buat CanonicalizedMNSHeaders. |
| CanonicalizedResource | Path URI dan string kueri, tanpa domain dan port. Contohnya, http://123.123.XX.XX:8080/api/test?code=200 menghasilkan /api/test?code=200. |
Jika gerbang atau gerbang transit lain dikonfigurasi dalam sistem Anda, gunakan URI asli dari permintaan HTTP, bukan URI yang telah dimodifikasi oleh gerbang.
Buat CanonicalizedMNSHeaders
Urutkan semua header x-mns-* secara alfabetis berdasarkan nama huruf kecil, lalu gabungkan:
// Ambil header permintaan
Map<String, String> httpHeaders = request.getHeaders();
// Urutkan header dan ubah nama menjadi huruf kecil
sortHeadersKeyAndToLowerCase(httpHeaders);
// Gabungkan header x-mns-*
Set<String> keySet = httpHeaders.keySet();
for (String key : keySet) {
if (key.startsWith("x-mns-")) {
CanonicalizedMNSHeaders.append(key).append(":")
.append(httpHeaders.get(key)).append("\n");
}
}Contoh string-to-sign
Berdasarkan contoh permintaan di atas, string-to-sign yang dihasilkan adalah:
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
/notificationsLangkah 3: Dekripsi header Authorization
Base64-decode nilai header Authorization, lalu dekripsi byte hasil decode dengan kunci publik RSA dari Langkah 1.
Langkah 4: Bandingkan dan verifikasi
Bandingkan string-to-sign dari Langkah 2 dengan nilai hasil dekripsi dari Langkah 3:
Sesuai: Permintaan autentik. Proses pesannya.
Tidak sesuai: Permintaan mungkin dipalsukan. Tolak permintaan tersebut.
Kode contoh Java
Contoh berikut menggabungkan keempat langkah tersebut. Metode authenticate mengambil sertifikat penandatangan, membuat string-to-sign, dan memverifikasi signature RSA-SHA1.
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 {
// Langkah 1: Ambil URL sertifikat penandatangan dari header permintaan
if (!headers.containsKey("x-mns-signing-cert-url")) {
System.out.println("Header x-mns-signing-cert-url tidak ditemukan");
return false;
}
String cert = headers.get("x-mns-signing-cert-url");
if (cert.isEmpty()) {
System.out.println("x-mns-signing-cert-url kosong");
return false;
}
// Base64-decode nilai header untuk mendapatkan URL sertifikat sebenarnya
cert = new String(Base64.decodeBase64(cert));
System.out.println("x-mns-signing-cert-url:\t" + cert);
// Unduh sertifikat dan ekstrak kunci publik
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();
// Langkah 2: Buat string-to-sign
String str2sign = getSignStr(method, uri, headers);
System.out.println("String2Sign:\t" + str2sign);
// Langkah 3: Base64-decode header Authorization
String signature = headers.get("Authorization");
byte[] decodedSign = Base64.decodeBase64(signature);
// Langkah 4: Verifikasi signature RSA-SHA1
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("Hasil autentikasi:" + res);
}
}