RabbitMQ指南(七) SSL\TLS通信
7.1 證書生成和配置
7.2 Java客戶端
7.3 C#客戶端
7.1 證書生成和配置
? RabbitMQ對外提供服務(wù)時(shí),為保證通信的安全性,通常使用SSL/TLS加密通信。
? 關(guān)于非對稱加密、SSL\TLS協(xié)議、證書授權(quán)中心(Certificate Authority,CA)可參考其他資料,本文僅演示RabbitMQ SSL\TLS通信的具體操作。對于生成證書,本文使用RabbitMQ官網(wǎng)推薦的tls-gen工具,該工具可在MacOS和Linux系統(tǒng)中使用,要求系統(tǒng)中裝有openssl和Python 3.5以上版本。
? 使用tls-gen生成證書:
# 下載,也可手工從以下網(wǎng)址下載后上傳至服務(wù)器
git clone https://github.com/michaelklishin/tls-gen tls-gen
cd tls-gen/basic
# 123456是自定義的私鑰密碼,客戶端會用到
make PASSWORD=123456
make verify
make info
1234567
? 證書生成完畢后,會在basic目錄下生成result、testca、server和client四個(gè)文件夾。
? 通常使用單向認(rèn)證的方式進(jìn)行SSL\TLS通信,所需的文件為:
??。?)CA的證書文件testca/cacert.cer,C#客戶端需要信任簽名的CA;
??。?)CA證書文件result/ca_certificate.pem,用于服務(wù)端配置;
??。?)服務(wù)端證書文件result/server_certificate.pem,用于服務(wù)端配置;
? (4)服務(wù)端私鑰文件result/server_key.pem,用于服務(wù)端配置;
??。?)客戶端證書文件result/client_key.p12,用于客戶端。
? 在RabbitMQ解壓目錄的etc/rabbitmq/路徑下,加入文件rabbitmq.conf(本文使用新格式配置文件,舊格式相應(yīng)配置可自行參照官網(wǎng)的示例文件)。該路徑為RabbitMQ默認(rèn)配置文件路徑,也可以RABBITMQ_CONFIG_FILE環(huán)境變量指定配置文件的路徑。
? rabbitmq.conf內(nèi)容:
# 限定非SSL\TLS的通信僅可在服務(wù)端本地連接
listeners.tcp.default = 127.0.0.1:5672
# SSL\TLS通信的端口
listeners.ssl.default = 5671
# 服務(wù)端私鑰和證書文件配置
ssl_options.cacertfile = /root/tls-gen/basic/result/ca_certificate.pem
ssl_options.certfile = /root/tls-gen/basic/result/server_certificate.pem
ssl_options.keyfile = /root/tls-gen/basic/result/server_key.pem
# 有verify_none和verify_peer兩個(gè)選項(xiàng),verify_none表示完全忽略驗(yàn)證證書的結(jié)果,verify_peer表示要求驗(yàn)證對方證書
ssl_options.verify = verify_peer
# 若為true,服務(wù)端會向客戶端索要證書,若客戶端無證書則中止SSL握手;若為false,則客戶端沒有證書時(shí)依然可完成SSL握手
ssl_options.fail_if_no_peer_cert = true
123456789101112
7.2 Java客戶端
? 使用Java提供的keytool工具,將服務(wù)端證書server_certificate.pem轉(zhuǎn)換為keystore證書文件:
keytool -import -alias rabbitmqserver -file F:/server_certificate.pem -keystore F:/server.keystore
? 其中,“F:/server_certificate.pem”是服務(wù)端證書文件路徑,“F:/server.keystore”是生成的keystore證書文件的路徑。生成文件時(shí)要求設(shè)置一個(gè)密鑰庫口令,本文設(shè)置為“abcdef”。
? Java客戶端如下:
import java.io.FileInputStream;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Sender {
// RabbitMQ服務(wù)端地址、端口、用戶名、密碼
private static final String ADDRESS = "10.176.64.227";
private static final int PORT = 5671;
private static final String USERNAME = "mqtester";
private static final String PASSWORD = "mqtester";
private static final String DEFAULT_EXCHANGE = "";
private static final String QUEUE_NAME = "Queue_Java";
// 使用tls-gen工具生成證書文件時(shí)設(shè)置的私鑰密碼
private static final String CLIENT_KEYSTORE_PASSWORD = "123456";
// 客戶端證書文件client_key.p12路徑
private static final String CLIENT_KEYSTORE_PATH = System.getProperty("user.dir") + "\\client_key.p12";
// 使用keytool生成證書文件時(shí)填寫的密碼
private static final String SERVER_KEYSTORE_PASSWORD = "abcdef";
// 使用keytool生成的服務(wù)端證書文件路徑
private static final String SERVER_KEYSTORE_PATH = System.getProperty("user.dir") + "\\server.keystore";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(ADDRESS);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
// 啟用SSL\TLS
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream(CLIENT_KEYSTORE_PATH), CLIENT_KEYSTORE_PASSWORD.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, CLIENT_KEYSTORE_PASSWORD.toCharArray());
KeyStore tks = KeyStore.getInstance("JKS");
tks.load(new FileInputStream(SERVER_KEYSTORE_PATH), SERVER_KEYSTORE_PASSWORD.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(tks);
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
factory.useSslProtocol(ctx);
// 發(fā)送消息,測試連接
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
byte[] message = ("test message").getBytes();
channel.basicPublish(DEFAULT_EXCHANGE, QUEUE_NAME, null, message);
channel.close();
connection.close();
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
7.3 C#客戶端
? 由于使用tls-gen生成證書時(shí),是自己作為CA進(jìn)行簽名的,需將CA的證書導(dǎo)入受信任的根證書頒發(fā)機(jī)構(gòu)。
? 運(yùn)行“certmgr.msc”:
? 將testca/cacert.cer文件導(dǎo)入受信任的根證書頒發(fā)機(jī)構(gòu):
? 證書導(dǎo)入后如下:
? C#客戶端如下:
public class Sender
{
// RabbitMQ服務(wù)端地址、端口、用戶名、密碼
private const string ADDRESS = "10.176.64.227";
private const int PORT = 5671;
private const string USERNAME = "mqtester";
private const string PASSWORD = "mqtester";
private const string DEFAULT_EXCHANGE = "";
private const string QUEUE_NAME = "Queue_C#";
// 使用tls-gen生成證書的服務(wù)器的主機(jī)名,該值必須填寫正確
private const string SERVER_NAME = "centos7";
// 客戶端證書文件client_key.p12路徑
private const string CLIENT_CERT_PATH = "F:\\client_key.p12";
// 使用tls-gen工具生成證書文件時(shí)設(shè)置的私鑰密碼
private const string CLIENT_CERT_PASSWORD = "123456";
public static void Main(string[] args)
{
var factory = new ConnectionFactory()
{
HostName = ADDRESS,
Port = PORT,
UserName = USERNAME,
Password = PASSWORD,
};
// 啟用SSL\TLS
factory.Ssl.ServerName = SERVER_NAME;
factory.Ssl.CertPath = CLIENT_CERT_PATH;
factory.Ssl.CertPassphrase = CLIENT_CERT_PASSWORD;
factory.Ssl.Enabled = true;
// 發(fā)送消息,測試連接
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(QUEUE_NAME, false, false, false, null);
byte[] message = Encoding.UTF8.GetBytes("test message");
channel.BasicPublish(DEFAULT_EXCHANGE, QUEUE_NAME, null, message);
}
}
}
}