使用同一套证书部署EMQX和HIVEMQ-CE

在对安全性要求比较高的业务,一般我们都需要部署SSL双向认证,下面我们以EMQX和HIVEMQ-CE为例,讲解如何使用同一套证书部署两个mqtt broker。

生成证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# 生成根证书密钥对
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  openssl genrsa -des3 -out rootCA.key 2048
# 输入保护密码
Enter PEM pass phrase: AVe384L3oCgN
# 再次输入密码,请保存好密码,在生成,签发和验证都需要
# 后面会有很多密码,如果搞不清楚,建议全部用一个密码
# 密码可以在 https://1password.com/zh-cn/password-generator 生成
Verifying - Enter PEM pass phrase: AVe384L3oCgN
# 生成根证书,有效期10年
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.crt
# 输入保护密码
Enter pass phrase for rootCA.key: AVe384L3oCgN
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
# 国家简称
Country Name (2 letter code) [AU]:CN
# 省份,格式随意
State or Province Name (full name) [Some-State]:Jiang Su
# 市,格式随意
Locality Name (eg, city) []:Su Zhou
# 公司:格式随意
Organization Name (eg, company) [Internet Widgits Pty Ltd]:YCMJ
# 部门:格式随意
Organizational Unit Name (eg, section) []:DEV
# CN:根证书写死rootCA
Common Name (e.g. server FQDN or YOUR name) []: rootCA
# 邮箱
Email Address []:caszhou86@gmail.com
# 生成服务端证书密钥对
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  openssl genrsa -out server.key 2048
# 创建服务端CSR配置
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  vim openssl.cnf
[req]
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = req_ext
x509_extensions = v3_req
prompt = no

[req_distinguished_name]
countryName = CN
stateOrProvinceName = Jiang Su
localityName = Su Zhou
organizationName = YCMJ
organizationalUnitName = DEV
# CN:必须是你server地址
commonName = mqtt-server.sipa.com

[req_ext]
subjectAltName = @alt_names

[v3_req]
subjectAltName = @alt_names

[alt_names]
# server 地址
DNS.1 = mqtt-server.sipa.com
# server ip
IP.1 = 127.0.0.1
# 生成CSR
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  openssl req -new -key server.key -config openssl.cnf -out server.csr
# 使用根证书,签发服务器证书,5年有效期
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 1825Certificate request self-signature ok
subject=C = CN, ST = Jiang Su, L = Su Zhou, O = YCMJ, CN = mqtt-server.sipa.com
Enter pass phrase for rootCA.key: AVe384L3oCgN
# 生成客户端证书密钥对
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  openssl genrsa -out client.key 2048
# 生成客户端CSR
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  openssl req -new -key client.key -out client.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Jiang Su
Locality Name (eg, city) []:Su Zhou
Organization Name (eg, company) [Internet Widgits Pty Ltd]:YCMJ
Organizational Unit Name (eg, section) []:DEV
# 客户端FQDN或者名字
Common Name (e.g. server FQDN or YOUR name) []:client
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
# 使用根证书签发客户端证书,有效期1年
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  openssl x509 -req -in client.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out client.crt -days 365
Certificate request self-signature ok
subject=C = CN, ST = Jiang Su, L = Su Zhou, O = YCMJ, OU = DEV, CN = client
Enter pass phrase for rootCA.key: AVe384L3oCgN
# 验证证书
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  openssl verify -CAfile rootCA.crt server.crt
server.crt: OK
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  openssl verify -CAfile rootCA.crt client.crt
client.crt: OK

部署EMQX

使用docker部署,命令如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
docker run \
--name emqx_5.7.1 \
-p 1883:1883 \
# ssl监听端口
-p 8883:8883 \
-p 8083:8083 \
-p 18083:18083 \
-v /Users/zhouxiajie/Docker/emqx/data:/opt/emqx/data \
-v /Users/zhouxiajie/Docker/emqx/log:/opt/emqx/log \
-v /Users/zhouxiajie/JetBrains/idea_home/ycmj/tcp/platform-level/certs/cert/rootCA.crt:/opt/emqx/etc/certs/rootCAs.pem \
-v /Users/zhouxiajie/JetBrains/idea_home/ycmj/tcp/platform-level/certs/cert/server.crt:/opt/emqx/etc/certs/server-cert.pem \
-v /Users/zhouxiajie/JetBrains/idea_home/ycmj/tcp/platform-level/certs/cert/server.key:/opt/emqx/etc/certs/server-key.pem \
-v /Users/zhouxiajie/JetBrains/idea_home/ycmj/tcp/platform-level/emqx/emqx.conf:/opt/emqx/etc/emqx.conf \
-d \
emqx:5.7.1

配置文件内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
## NOTE:
## This config file overrides data/configs/cluster.hocon,
## and is merged with environment variables which start with 'EMQX_' prefix.
##
## Config changes made from EMQX dashboard UI, management HTTP API, or CLI
## are stored in data/configs/cluster.hocon.
## To avoid confusion, please do not store the same configs in both files.
##
## See https://www.emqx.io/docs/en/latest/configuration/configuration.html for more details.
## Configuration full example can be found in etc/examples

node {
name = "emqx@127.0.0.1"
cookie = "emqxsecretcookie"
data_dir = "data"
}

cluster {
name = emqxcl
discovery_strategy = manual
}

## EMQX provides support for two primary log handlers: `file` and `console`, with an additional `audit` handler specifically designed to always direct logs to files.
## The system's default log handling behavior can be configured via the environment variable `EMQX_DEFAULT_LOG_HANDLER`, which accepts the following settings:
##
## - `file`: Directs log output exclusively to files.
## - `console`: Channels log output solely to the console.
##
## It's noteworthy that `EMQX_DEFAULT_LOG_HANDLER` is set to `file` when EMQX is initiated via systemd `emqx.service` file.
## In scenarios outside systemd initiation, `console` serves as the default log handler.

## Read more about configs here: https://www.emqx.io/docs/en/latest/configuration/logs.html

log {
# file {
# level = warning
# }
# console {
# level = warning
# }
}
dashboard {
listeners.http {
bind = 18083
}
}

listeners.ssl.default {
bind = "0.0.0.0:8883"
ssl_options {
# PEM 格式的文件,包含一个或多个用于验证客户端证书的根 CA 证书
# 单向认证时,该文件内容可以为空
cacertfile = "etc/certs/rootCAs.pem"
# PEM 格式的服务器证书,如果证书不是直接由根 CA 签发,那么中间 CA 的证书必须加在服务器证书的后面组成一个证书链
certfile = "etc/certs/server-cert.pem"
# PEM 格式的密钥文件
keyfile = "etc/certs/server-key.pem"
# 设置成 'verify_peer' 来验证客户端证书是否为 cacertfile 中某个根证书签发。双向认证时,必须设置成 'verify_peer'。
# 设置成 'verify_none' 则不验证客户端证书,即单向认证。
verify = verify_peer
# 如果设置成 true,但是客户端在握手时候没有发送证书,服务端会终止握手。双向认证时,必须设置成 true。
# 如果设置成 false,那么服务端只有在客户端发送一个非法证书时才会终止握手
fail_if_no_peer_cert = true
}
}

MQTTX测试

首先测试1883端口,配置如下

配置

测试成功

测试结果

测试8883端口,配置如下

配置

测试成功

测试结果

java paho客户端测试

maven pom dependency配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
</dependency>
</dependencies>

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package com.sipa.test.ut;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileReader;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.junit.jupiter.api.Test;

/**
* work
*/
public class PahoMQTTClientSSLTest4CertEMQX {
@Test
public void test() {
String serverUrl = "ssl://mqtt-server.sipa.com:8883";
String folder = "/Users/zhouxiajie/JetBrains/idea_home/ycmj/tcp/platform-level/certs/cert/";
String caFilePath = folder + "rootCA.crt";
String clientCrtFilePath = folder + "client.crt";
String clientKeyFilePath = folder + "client_traditional.key";
String mqttUserName = "admin";
String mqttPassword = "public";

MqttClient client;
try {
client = new MqttClient(serverUrl, "TestMqtt");

MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(mqttUserName);
options.setPassword(mqttPassword.toCharArray());
options.setConnectionTimeout(60);
options.setKeepAliveInterval(60);
options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1);

SSLSocketFactory socketFactory =
getSocketFactory(caFilePath, clientCrtFilePath, clientKeyFilePath, "AVe384L3oCgN");
options.setSocketFactory(socketFactory);

System.out.println("starting connect the server...");
client.connect(options);

System.out.println("connected!");
Thread.sleep(1000);

client.subscribe("test", 0);
client.disconnect();
System.out.println("disconnected!");
} catch (MqttException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}

private static SSLSocketFactory getSocketFactory(final String caCrtFile, final String crtFile, final String keyFile,
final String password) throws Exception {
Security.addProvider(new BouncyCastleProvider());

// load CA certificate
X509Certificate caCert = null;

FileInputStream fis = new FileInputStream(caCrtFile);
BufferedInputStream bis = new BufferedInputStream(fis);
CertificateFactory cf = CertificateFactory.getInstance("X.509");

while (bis.available() > 0) {
caCert = (X509Certificate)cf.generateCertificate(bis);
}

// load client certificate
bis = new BufferedInputStream(new FileInputStream(crtFile));
X509Certificate cert = null;
while (bis.available() > 0) {
cert = (X509Certificate)cf.generateCertificate(bis);
}

// load client private key
PEMParser pemParser = new PEMParser(new FileReader(keyFile));
Object object = pemParser.readObject();
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
KeyPair key;
if (object instanceof PEMEncryptedKeyPair) {
System.out.println("Encrypted key - we will use provided password");
key = converter.getKeyPair(((PEMEncryptedKeyPair)object).decryptKeyPair(decProv));
} else {
System.out.println("Unencrypted key - no password needed");
key = converter.getKeyPair((PEMKeyPair)object);
}
pemParser.close();

// CA certificate is used to authenticate server
KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
caKs.load(null, null);
caKs.setCertificateEntry("ca-certificate", caCert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(caKs);

// client key and certificates are sent to server so it can authenticate
// us
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry("certificate", cert);
ks.setKeyEntry("private-key", key.getPrivate(), password.toCharArray(),
new java.security.cert.Certificate[] {cert});
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password.toCharArray());

// finally, create SSL socket factory
SSLContext context = SSLContext.getInstance("TLSv1.2");
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

return context.getSocketFactory();
}
}

运行后输出如下

1
2
3
4
Unencrypted key - no password needed
starting connect the server...
connected!
disconnected!

java mica客户端测试

mica客户端可以使用jks,pfk或p12的证书,这里我们以常用的jks为例,首先我们需要生成两个jks文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 将客户端证书导出为p12类型
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name client -CAfile rootCA.crt -caname root -password pass:AVe384L3oCgN
# 将p12证书导入到密钥库中
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  keytool -importkeystore -deststorepass AVe384L3oCgN -destkeypass AVe384L3oCgN -destkeystore client-ks.jks -srckeystore client.p12 -srcstoretype PKCS12 -srcstorepass AVe384L3oCgN
正在将密钥库 client.p12 导入到 client-ks.jks...
已成功导入别名 client 的条目。
已完成导入命令: 1 个条目成功导入, 0 个条目失败或取消
# 将根证书导入到信任库中
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  keytool -import -file rootCA.crt -alias root -keystore cert-ts.jks -storepass AVe384L3oCgN
所有者: EMAILADDRESS=caszhou86@gmail.com, CN=rootCA, OU=DEV, O=YCMJ, L=Su Zhou, ST=Jiang Su, C=CN
发布者: EMAILADDRESS=caszhou86@gmail.com, CN=rootCA, OU=DEV, O=YCMJ, L=Su Zhou, ST=Jiang Su, C=CN
序列号: 45648d80b7137e4caf61e8de43708dbdd9b45d40
生效时间: Mon Jan 27 11:02:33 CST 2025, 失效时间: Thu Jan 25 11:02:33 CST 2035
证书指纹:
SHA1: 04:63:4C:2A:FD:BC:89:3F:15:75:27:7C:23:4E:95:D0:DB:59:00:CF
SHA256: F1:3F:1E:4F:75:B3:32:F0:29:32:DB:21:88:E4:0C:4A:DA:2A:EA:E3:CB:3C:41:1E:BC:80:E3:25:A3:D3:61:E6
签名算法名称: SHA256withRSA
主体公共密钥算法: 2048 位 RSA 密钥
版本: 3

扩展:

#1: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: E6 B9 A2 7D 82 9C AA 84 B2 35 84 7E 1A 15 22 C2 .........5....".
0010: C0 C9 A9 38 ...8
]
]

#2: ObjectId: 2.5.29.19 Criticality=true
BasicConstraints:[
CA:true
PathLen:2147483647
]

#3: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: E6 B9 A2 7D 82 9C AA 84 B2 35 84 7E 1A 15 22 C2 .........5....".
0010: C0 C9 A9 38 ...8
]
]

是否信任此证书? [否]: 是
证书已添加到密钥库中

接下来我们用java代码做测试,maven pom dependency配置如下

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-client-spring-boot-starter</artifactId>
<version>2.4.2</version>
</dependency>
</dependencies>

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.sipa.test;

import org.dromara.mica.mqtt.core.client.IMqttClientConnectListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;
import org.tio.core.ChannelContext;

import lombok.extern.slf4j.Slf4j;

@SpringBootApplication
public class MicaMQTTClientSSLApplication {
public static void main(String[] args) {
SpringApplication.run(MicaMQTTClientSSLApplication.class, args);
}
}

@Slf4j
@Component
class MqttClientConnectListener implements IMqttClientConnectListener {
@Value("${mqtt.client.clientId:}")
private String mqttClientId;

@Override
public void onConnected(ChannelContext channelContext, boolean isReconnect) {
if (isReconnect) {
log.info("重连 mqtt 服务器成功 mqttClientId={}", mqttClientId);
} else {
log.info("连接 mqtt 服务器成功 mqttClientId={}", mqttClientId);
}
}

@Override
public void onDisconnect(ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove) {
log.error("mqtt 连接断开 remark:{} isRemove:{}", remark, isRemove, throwable);
}
}

spring配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
spring.application.name: demo
server.port: 6088
mqtt:
client:
enabled: true
ip: mqtt-server.sipa.com
port: 8883
name: Mica-Mqtt-Client
client-id: 000001
user-name: admin
password: pubic
global-subscribe:
timeout: 5
reconnect: true
re-interval: 5000
version: mqtt_3_1
read-buffer-size: 8KB
max-bytes-in-message: 10MB
keep-alive-secs: 60
clean-session: true
ssl:
enabled: true
keystore-path: /Users/zhouxiajie/JetBrains/idea_home/ycmj/tcp/platform-level/certs/cert/client-ks.jks
keystore-pass: AVe384L3oCgN
truststore-path: /Users/zhouxiajie/JetBrains/idea_home/ycmj/tcp/platform-level/certs/cert/cert-ts.jks
truststore-pass: AVe384L3oCgN

运行后输出如下

1
2
2025-01-27 13:49:26.360  INFO 17863 --- [    tio-group-2] o.t.client.ConnectionCompletionHandler   : connected to mqtt-server.sipa.com:8883
2025-01-27 13:49:26.361 INFO 17863 --- [ tio-group-2] org.tio.core.ssl.SslFacadeContext : server:mqtt-server.sipa.com:8883, client:127.0.0.1:53269 开始SSL握手

部署HIVEMQ-CE

hivemq-ce部署需要jks类型的证书才能部署,trust store可以继续使用为mica客户端生产的cert-ts.jks,我们只要生成服务端的密钥库

1
2
3
4
5
# 将服务端证书转化为p12格式
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  openssl pkcs12 -export -in server.crt -inkey server.key -out server.p12 -name server -CAfile rootCA.crt -caname root -password pass:AVe384L3oCgN
# p12格式证书转换为jks
(base) zhouxiajie@zhouxiajiedeMBP  ~/Downloads/blog  keytool -importkeystore -deststorepass AVe384L3oCgN -destkeypass AVe384L3oCgN -destkeystore server-ks.jks -srckeystore server.p12 -srcstoretype PKCS12 -srcstorepass AVe384L3oCgN -alias server
正在将密钥库 server.p12 导入到 server-ks.jks...

hivemq换个方式部署,使用compose,配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
services:
hivemq:
image: hivemq/hivemq-ce:latest
container_name: hivemq-ce
ports:
- "11883:1883"
- "18883:8883"
- "18080:8080"
- "18000:8000"
volumes:
- ./conf/config_jks.xml:/opt/hivemq/conf/config.xml
- /Users/zhouxiajie/JetBrains/idea_home/ycmj/tcp/platform-level/certs/cert/server-ks.jks:/opt/hivemq/conf/hivemq.jks
- /Users/zhouxiajie/JetBrains/idea_home/ycmj/tcp/platform-level/certs/cert/cert-ts.jks:/opt/hivemq/conf/hivemq-trust-store.jks
- ./extension:/opt/hivemq/extensions
environment:
HIVEMQ_BIND_ADDRESS: 0.0.0.0
HIVEMQ_LOG_LEVEL: DEBUG

hivemq-ce 配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?xml version="1.0"?>
<hivemq>

<listeners>
<tcp-listener>
<port>1883</port>
<bind-address>0.0.0.0</bind-address>
</tcp-listener>

<tls-tcp-listener>
<port>8883</port>
<bind-address>0.0.0.0</bind-address>
<proxy-protocol>true</proxy-protocol>
<tls>
<keystore>
<path>/opt/hivemq/conf/hivemq.jks</path>
<password>AVe384L3oCgN</password>
<private-key-password>AVe384L3oCgN</private-key-password>
</keystore>
<client-authentication-mode>REQUIRED</client-authentication-mode>
<truststore>
<path>/opt/hivemq/conf/hivemq-trust-store.jks</path>
<password>AVe384L3oCgN</password>
</truststore>
</tls>
</tls-tcp-listener>

<websocket-listener>
<port>8000</port>
<bind-address>0.0.0.0</bind-address>
<path>/mqtt</path>
<subprotocols>
<subprotocol>mqttv3.1</subprotocol>
<subprotocol>mqtt</subprotocol>
</subprotocols>
</websocket-listener>
</listeners>

<mqtt>
<queued-messages>
<max-queue-size>1000</max-queue-size>
<strategy>discard</strategy>
</queued-messages>

<message-expiry>
<max-interval>4294967296</max-interval>
</message-expiry>
</mqtt>

<security>
<allow-anonymous>false</allow-anonymous>
</security>

<persistence>
<enabled>true</enabled>
<folder>/opt/hivemq/data</folder>
</persistence>

<restrictions>
<max-connections>-1</max-connections>
<max-client-id-length>65535</max-client-id-length>
<max-topic-length>65535</max-topic-length>
</restrictions>

</hivemq>

MQTTX测试

首先测试11883端口,配置如下

配置

测试成功

结果

测试18883端口,配置如下

配置

测试成功

结果

java paho客户端测试

复制对应emqx的代码,修改如下

代码diff

运行结果同上

java mica客户端测试

复制对应emqx的配置,修改如下

代码diff

运行结果同上

总结

EMQX 部署时可以使用最常见的证书格式,而 HiveMQ CE 由于是基于 Java 开发,因此需要使用 Java 特定的证书格式。

在选择客户端时,可以使用现成的封装版本,也可以根据具体场景自定义封装。