docker安装Emqx并使用自签名证书开启 SSL/TLS 连接

docker安装Emqx并使用自签名证书开启 SSL/TLS 连接

详细操作过程可以参考官方说明文档:官方说明文档

客户端TLS接入示例:客户端接入示例

从官方的文档中我们了解到,EMQX支持通过 X.509 证书实现单向和双向客户端/服务器互信认证,这里我们使用单项认证。

认证方式 说明 验证方式 优缺点
单向认证 客户端验证服务器身份,但服务器不验证客户端的身份 客户端通常不需要提供证书,仅需验证服务器的证书是否由受信任的证书颁发机构(CA)签发 只能实现通信数据的机密性和完整性,但无法保证通信双方的身份
双向认证 服务器和客户端彼此验证对方的身份 需要为每个设备签发证书,服务器验证客户端的证书以确认其身份的合法性 可以确保服务器和客户端之间的互信关系,并防止中间人攻击

一、获取自签名证书

1、创建openssl.cnf文件

[ req ]
default_bits        = 2048
default_keyfile     = server-key.pem
distinguished_name  = req_distinguished_name
x509_extensions     = v3_req
prompt = no

[ req_distinguished_name ]
C = CN
ST = California
L = San Francisco
O = Example Organization
OU = Example Unit
CN = 192.168.3.51  # 这里设置为服务器的IP地址

[ v3_req ]
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = 192.168.3.51  # 这里设置为服务器的IP地址
IP.1 = 192.168.3.51   # 这里设置为服务器的IP地址

2、生成证书自签名证书

在openssl.cnf目录下执行如下命令生成自签名证书:

bash 复制代码
openssl genpkey -algorithm RSA -out server-key.pem
openssl req -new -key server-key.pem -out server.csr -config openssl.cnf
openssl x509 -req -in server.csr -out server-cert.pem -signkey server-keyi.pem -days 3650 -extensions v3_req -extfile openssl.cnf

二、docker安装EMQX

1、初始化目录

bash 复制代码
mkdir -p /wz_conf/emqx/etc
mkdir -p /wz_conf/emqx/data
mkdir -p /wz_conf/emqx/etc

2、加载镜像文件并挂载相应的文件目录

bash 复制代码
docker load -i emqx.tar.gz
这里也可以使用docker pull emqx/emqx:latest拉取镜像包
bash 复制代码
cd /wz_conf/emqx  
chmod 777 ./*  
docker run -d --privileged=true --name emqx emqx/emqx:latest
# 将容器中的相关配置文件挂在到物理机,方便后续修改,*******则为容器id
docker cp *********:/opt/emqx/etc /wz_conf/emqx  
docker stop *******
docker rm *******

3、启动docker容器

bash 复制代码
docker run -d \
--privileged=true \
--restart=always \
--name emqx \
-p 1883:1883 \
-p 8883:8883 \
-p 8083:8083 \
-p 8084:8084 \
-p 8081:8081 \
-p 18083:18083 \
-v /wz_conf/emqx/etc:/opt/emqx/etc \
-v /wz_conf/emqx/data:/opt/emqx/data \
-v /wz_conf/emqx/log:/opt/emqx/log \
emqx/emqx:latest

这里就不再详细说明了,如果不需要开启SSL/TLS,到这里也就可以了。

4、EMQX加载自签名证书

这里我是用的EMQX是5.8.0版本的,通过修改emqx.conf配置文件实现,也可以通过官方文档说的在dashboard中配置。

将生成的证书文件挂载到容器内/opt/emqx/etc/certs目录下,修改/opt/emqx/etc/emqx.conf配置文件末尾添加如下内容:

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

配置后重启docker容器即可。重启后也可以通过dashboard查看

三、客户端MQTTX连接测试

四、Springboot整合

bash 复制代码
<!-- mqtt -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-stream</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-mqtt</artifactId>
</dependency>

核心代码块

MqttPushCallback.java

bash 复制代码
import com.webuild.ai.outside.service.OutsideBaseService;
import com.webuild.ai.utils.WelMaxEncryptionUtil;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Collection;

@Component
@Slf4j
public class MqttPushCallback implements MqttCallback {

    @Autowired
    private MqttConfig mqttConfig;
    private static MqttClient client;

    @Override
    public void connectionLost(Throwable throwable) {
        log.info("连接断开,可以做重连");// 连接丢失后,一般在这里面进行重连
        if (client == null || !client.isConnected()) {
            mqttConfig.getMqttPushClient();
        }
    }

    @Override
    public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
        byte[] payload = mqttMessage.getPayload();
        String mqttMessagePayload = new String(payload);// 接收消息内容(转为字符串格式)
        // TODO 自己的处理逻辑
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
        log.info("deliveryComplete---------" + iMqttDeliveryToken.isComplete());
    }
}

MqttPushClient.java

bash 复制代码
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;

@Component
@Slf4j
public class MqttPushClient {

    @Autowired
    private MqttPushCallback mqttPushCallback;

    private static MqttClient client;

    public MqttClient getClient() {
        return client;
    }

    private static void setClient(MqttClient client) {
        MqttPushClient.client = client;
    }

    /**
     * 客户端连接
     *
     * @param host      ip+端口
     * @param clientID  客户端Id
     * @param username  用户名
     * @param password  密码
     * @param timeout   超时时间
     * @param keepalive 保留数
     */
    public void connect(String host, String clientID, String username, String password, int timeout, int keepalive) {
        MqttClient client;
        try {
            client = new MqttClient(host, clientID, new MemoryPersistence());
            MqttConnectOptions options = new MqttConnectOptions();
            if(host.startsWith("ssl")) {
                // 配置忽略 CN 验证
                SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
                sslContext.init(null, new TrustManager[]{
                        new X509TrustManager() {
                            @Override
                            public void checkClientTrusted(X509Certificate[] chain, String authType) {
                            }
                            @Override
                            public void checkServerTrusted(X509Certificate[] chain, String authType) {
                            }
                            @Override
                            public X509Certificate[] getAcceptedIssuers() {
                                return new X509Certificate[0];
                            }
                        }
                }, new java.security.SecureRandom());
                options.setSocketFactory(sslContext.getSocketFactory());
            }
            options.setCleanSession(true);
            options.setUserName(username);
            options.setPassword(password.toCharArray());
            options.setConnectionTimeout(timeout);
            options.setKeepAliveInterval(keepalive);
            MqttPushClient.setClient(client);
            try {
                client.setCallback(mqttPushCallback);
                client.connect(options);
            } catch (Exception e) {
                log.error("设置mqttPushCallback异常. e={}",e);
            }
        } catch (Exception e) {
            log.error("mqtt链接异常. e={}", e);
        }
    }
}

MqttClientUtil.java

bash 复制代码
import com.webuild.ai.config.MqttPushClient;
import com.webuild.ai.config.SpringValueConfig;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class MqttClientUtil {

    @Autowired
    private MqttPushClient mqttPushClient;

    /**
     * 发布消息
     *
     * @Description
     * @Param pushMessage
     * @Param topic
     * @Return void
     * @Author Administrator
     * @Date 2024/9/2 11:13
     **/
    public void publish(String pushMessage, String topic) {
        try {
            log.info(">>>>>>>mqtt推送消息>>>>>>>>. pushMessage={}, topic={}", pushMessage, topic);
            publish(pushMessage, topic, 0, false);
        } catch (Exception e) {
            log.error("mqtt发布消息异常. topic={}, pushMessage={}", topic, pushMessage);
        }
    }

    /**
     * 发布消息
     * 
     * @Description 
     * @Param pushMessage
     * @Param topic
     * @Param qos
     * QoS 0: 至多一次(At most once) 消息会被尽力而为地传递,但不保证消息会被送达。也就是说,消息可能会丢失,不会有确认消息发送给发布者
     * 1: 至少一次(At least once) 消息会被至少传递一次。发布者发送消息后会等待接收方的确认(PUBACK)。如果没有收到确认,消息会被重新发送,直到收到确认消息为止。这意味着接收方可能会收到重复的消息。
     * 2: 只一次(Only once)消息确保只会传递一次。通过四步握手(四个消息交换:PUBLISH、PUBREC、PUBREL、PUBCOMP)来保证消息既不会丢失也不会重复。这是最可靠的传输模式,但也会带来最大的开销
     * @Param retained
     * True (保留消息):当retained设为true时,Broker会保留这条消息,当新的客户端订阅这个Topic时,Broker会立即将这条保留消息发送给订阅者。新的保留消息会覆盖之前的保留消息。
     * False (非保留消息):当retained设为false时,消息不会被Broker保留。只有在消息发布后已有订阅者能收到该消息,新的订阅者不会收到这个消息。
     * @Return void
     * @Author Administrator
     * @Date 2024/9/2 11:07     
     **/
    public void publish(String pushMessage, String topic, int qos, boolean retained) {
        MqttMessage message = new MqttMessage();
        message.setPayload(pushMessage.getBytes());
        message.setQos(qos);
        message.setRetained(retained);
        MqttTopic mqttTopic = mqttPushClient.getClient().getTopic(topic);
        if (null == mqttTopic) {
            log.error("topic is not exist");
        }
        MqttDeliveryToken token;//Delivery:配送
        synchronized (this) {//注意:这里一定要同步,否则,在多线程publish的情况下,线程会发生死锁
            try {
                token = mqttTopic.publish(message);//也是发送到执行队列中,等待执行线程执行,将消息发送到消息中间件
//                token.waitForCompletion(5000L);
            } catch (Exception e) {
                log.error("mqtt publish error:={}", e);
            }
        }
    }
}
相关推荐
fendouweiqian8 分钟前
python请求SSL问题
网络·python·ssl
drebander15 分钟前
Netty 的 SSL/TLS 安全通信
网络·netty·ssl
weixin_399380691 小时前
Tongweb7049M4有关SSL/TLS 服务器瞬时 Diffie-Hellman 公共密钥过弱的处理方案(by lqw)
服务器·网络协议·ssl
Boforest4 小时前
centos9升级OpenSSH
centos·ssh·ssl
chenzfp2 天前
https ssl免费证书申请,自动续期,acme、certd
网络协议·https·ssl
朴拙数科2 天前
No module named ‘_ssl‘ No module named ‘_ctypes‘
chrome·网络协议·ssl
小蜜蜂爱编程3 天前
使用paho.mqtt.cpp库实现ssl/tls加密通信
网络·网络协议·中间件·ssl
Heart&Fire3 天前
https/http访问接口工具类,附带ssl忽略证书验证,以及head头部的添加-java版
http·https·ssl
xyhzczc3 天前
关于springBoot+vue项目中配置SSL证书问题
vue.js·spring boot·ssl