EMQX + SSL双向认证配置 JAVA 连接验证

该博客详细介绍了如何在EMQX中配置SSL双向认证,包括生成CA证书、服务端和客户端证书,修改EMQX配置文件,启用用户名密码验证,以及使用MQTT.fx客户端进行验证。还提供了Java代码示例,展示如何处理证书以进行安全连接。
摘要由CSDN通过智能技术生成
#!/bin/bash
# author:xijin.c

read -r -p "请输入文件存储目录名: " dir
cd /data
mkdir $dir
cd $dir


# 生成CA key【采用2048字节】
openssl genrsa -out ca.key 2048
# 生成CA 证书【默认3650天】
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -subj "/CN=www.emqx.io" -out ca.pem


read -r -p "请输入服务端ip: " serverIp
openssl genrsa -out server.key 2048
# 注意将IP修改为服务器IP
openssl req -new -key ./server.key -out server.csr -subj "/CN=$serverIp"
openssl x509 -req -in ./server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem -days 3650 -sha256

read -r -p "请输入客户端ip: " clientIp
openssl genrsa -out client.key 2048
#注意将IP修改为客户端IP
openssl req -new -key ./client.key -out client.csr -subj "/CN=$clientIp"
openssl x509 -req -in ./client.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client.pem -days 3650 -sha256


#将ca.pem 与 client.pem 转化为.crt格式
openssl x509 -outform der -in ca.pem -out ca.crt
openssl x509 -outform der -in client.pem -out client.crt
#将client.key转换为.pem文件【java代码连接需要】
openssl pkcs8 -topk8 -inform PEM -in client.key -outform PEM -nocrypt -out client-key-pkcs8.pem


 

 

 

1.生成CA key和证书

 cd /etc/pki/CA  
 生成CA key【采用2048字节】
 openssl genrsa -out ca.key 2048
 生成CA 证书【默认3650天】
 openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -subj "/CN=www.emqx.io" -out ca.pem

2.生成服务端key和证书

 openssl genrsa -out server.key 2048
 注意将IP修改为服务器IP
 openssl req -new -key ./server.key -out server.csr -subj "/CN=服务器IP"
 openssl x509 -req -in ./server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem -days 3650 -sha256

3.生成客户端key和证书

openssl genrsa -out client.key 2048
注意将IP修改为客户端IP
openssl req -new -key ./client.key -out client.csr -subj "/CN=服务器IP"
openssl x509 -req -in ./client.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client.pem -days 3650 -sha256
​

4.修改emqx配置文件emqx.conf 我这里emqx docker 镜像版本为: emqx/emqx 4.2.2

将生成的CA文件复制到/opt/emqx/etc/certs目录下:docker cp server.pem emqx:/opt/emqx/etc/certs/.

vi /opt/emqx/etc/emqx.conf
## SSL Options
listener.ssl.external.keyfile = /opt/emqx/etc/certs/server.key
listener.ssl.external.certfile = /opt/emqx/etc/certs/server.pem
## 开启双向认证
listener.ssl.external.cacertfile = /opt/emqx/etc/certs/ca.pem
listener.ssl.external.verify = verify_peer
listener.ssl.external.fail_if_no_peer_cert = true
​

 

启用用户名密码验证【可选】

关闭匿名认证,使用用户名密码认证

#先要把emq的匿名认证关了,在emqx.conf文件
allow_anonymous = false
**重启emqx服务**
​
#加载用户名认证插件 在docker 容器内部执行
cd /opt/emqx
​
./bin/emqx_ctl plugins load emqx_auth_username
#添加用户
./bin/emqx_ctl users add <Username> <Password>

5.使用MQTT.fx客户端验证是否配置成功

mqtt.fx下载地址:http://www.jensd.de/apps/mqttfx/

这里选用的是windows1.7.1的版本

 

MQTT.fx客户端使用

 填写用户名密码信息

在这里插入图片描述

 

**配置客户端ssl连接证书

java使用证书准备

cd 进入证书所在目录: /etc/pki/CA/
将ca.pem 与 client.pem 转化为.crt格式
openssl x509 -outform der -in your-ca.pem -out your-ca.crt
openssl x509 -outform der -in your-client.pem -out your-client.crt
​
将client.key转换为.pem文件【java代码连接需要】
openssl pkcs8 -topk8 -inform PEM -in client.key -outform PEM -nocrypt -out client-key-pkcs8.pem
​​

Java代码

<dependency>
    <groupId>org.eclipse.paho</groupId>
    <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
    <version>1.2.0</version>
</dependency>

MQTT静态常量类

  /**
   * MQTT静态参数常量类 
   */
   public class MqttConstant {
​    /**
     * CA证书
     */
    public final static String SSL_CA_CRT = "E:/工具/mqtt/方式2/ca.crt";
    /**
     * 客户端证书 client.crt证书文件路径
     */
    public final static String SSL_CLIENT_CRT = "E:/工具/mqtt/方式2/client.crt";
    /**
     * 客户端证书key client-key-pkcs8.pem证书文件路径
     */
    public final static String SSL_CLIENT_KEY_PKCS8_PEM = "E:/工具/mqtt/方式2/client-key-pkcs8.pem";
    /**
     * 客户端证书密码
     */
    public final static String SSL_CLIENT_PASSWORD = "";}

 

 

import org.apache.commons.codec.binary.Base64;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
​
public class EmqxSSLFactory {
​
    public static javax.net.ssl.SSLSocketFactory getSSLSocktet(String caPath, String crtPath, String keyPath, String password) {
        try{
            CertificateFactory cAf = CertificateFactory.getInstance("X.509");
            FileInputStream caIn = new FileInputStream(caPath);
            X509Certificate ca = (X509Certificate) cAf.generateCertificate(caIn);
            KeyStore caKs = KeyStore.getInstance("JKS");
            caKs.load(null, null);
            caKs.setCertificateEntry("ca-certificate", ca);
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
            tmf.init(caKs);
​
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            FileInputStream crtIn = new FileInputStream(crtPath);
            X509Certificate caCert = (X509Certificate) cf.generateCertificate(crtIn);
​
            crtIn.close();
            KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
            ks.load(null, null);
            ks.setCertificateEntry("certificate", caCert);
            ks.setKeyEntry("private-key", getPrivateKey(keyPath), password.toCharArray(),
                    new java.security.cert.Certificate[]{caCert});
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
            kmf.init(ks, password.toCharArray());
​
            SSLContext context = SSLContext.getInstance("TLSv1.2");
​
            context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
            return context.getSocketFactory();
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
​
    public static PrivateKey getPrivateKey(String path) throws Exception {
​
        org.apache.commons.codec.binary.Base64 base64 = new Base64();
        byte[] buffer = base64.decode(getPem(path));
​
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
​
    }
​
    private static String getPem(String path) throws Exception {
        FileInputStream fin = new FileInputStream(path);
        BufferedReader br = new BufferedReader(new InputStreamReader(fin));
        String readLine = null;
        StringBuilder sb = new StringBuilder();
        while ((readLine = br.readLine()) != null) {
            if (readLine.charAt(0) == '-') {
                continue;
            } else {
                sb.append(readLine);
                sb.append('\r');
            }
        }
        fin.close();
        return sb.toString();
    }
}

或者

    --需要引入低版本bouncycastle包 我这里jar冲突没有使用
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcpkix-jdk15on</artifactId>
        <version>1.47</version>
    </dependency>

 

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.openssl.PasswordFinder;
​
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.X509Certificate;
​
public class SslUtil {
​
    public static SSLSocketFactory getSocketFactory(final String caCrtFile, final String crtFile, final String keyFile,
                                                    final String password) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
​
        // load CA certificate
        PEMReader reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(caCrtFile)))));
        X509Certificate caCert = (X509Certificate)reader.readObject();
        reader.close();
​
        // load client certificate
        reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(crtFile)))));
        X509Certificate cert = (X509Certificate)reader.readObject();
        reader.close();
​
        // load client private key
        reader = new PEMReader(
                new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(keyFile)))),
                new PasswordFinder() {
                    @Override
                    public char[] getPassword() {
                        return password.toCharArray();
                    }
                }
        );
        KeyPair key = (KeyPair)reader.readObject();
        reader.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(TrustManagerFactory.getDefaultAlgorithm());
        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());
            
        //todo:这里会出现问题 我用的是jdk1.8 对应TLSv1.2 --- javax.net.ssl.SSLException: Received fatal alert: protocol_version
        // finally, create SSL socket factory 
        SSLContext context = SSLContext.getInstance("TLSv1.2");
        context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
​
        return context.getSocketFactory();
    }
}

 

连接验证

​
    public void connect(MqttConfiguration inCon) throws Exception {
        try {
            configuration = inCon;
            client = new MqttAsyncClient(configuration.getBrokerIp(),
                    configuration.getClientId(), new MemoryPersistence());
            client.setCallback(this);
            clientOptions = new MqttConnectOptions();
//            if (configuration.isSsl() && !StringUtils.isEmpty(configuration.getTruststore())) {
//                Properties sslProperties = new Properties();
//                sslProperties.put(SSLSocketFactoryFactory.TRUSTSTORE, configuration.getTruststore());
//                sslProperties.put(SSLSocketFactoryFactory.TRUSTSTOREPWD, configuration.getTruststorePassword());
//                sslProperties.put(SSLSocketFactoryFactory.TRUSTSTORETYPE, "JKS");
//                sslProperties.put(SSLSocketFactoryFactory.CLIENTAUTH, false);
//                clientOptions.setSSLProperties(sslProperties);
//            }
            MqttClientCredentialsFactory clientCredentialsFactory = new MqttClientCredentialsFactory();
            clientCredentialsFactory.getMqttClientCredentials(configuration.getAuth()).configure(clientOptions, configuration);
            clientOptions.setCleanSession(true);
            SSLSocketFactory factory = EmqxSSLFactory.getSSLSocktet(MqttConstant.SSL_CA_CRT, MqttConstant.SSL_CLIENT_CRT,
                    MqttConstant.SSL_CLIENT_KEY_PKCS8_PEM, MqttConstant.SSL_CLIENT_PASSWORD);
            clientOptions.setSocketFactory(factory);
            checkConnection();
        } catch (MqttException e) {
            log.error("{} MQTT broker connection failed!", configuration.getBrokerIp(), e);
            throw new RuntimeException("MQTT broker connection failed!", e);
        }
    }

 

添加ssl认证:【EMQ记录】指定的 SocketFactory 类型与代理程序 URI 不匹配问题

是因为MQTT-Client 使用了ssl连接,但是mqtt的URL,要使用SSL,不要使用TCP

例如

tcp://192.168.111:1883 替换为 ssl://192.168.111:8883

当您尝试通过 Java 连接EMQX 服务器并遇到无法连接的问题时,可能是由多种原因造成的。这里有几个常见的步骤可以帮助您诊断问题: ### 1. 检查配置文件和参数 首先确认您的 Java 客户端是否正确地配置EMQX 的 URL、用户名、密码以及客户端 ID。确保 `clientId` 是唯一的,并且不要与其他客户端冲突。 #### 相关问题 - 配置错误示例: - **URL 错误**:检查 MQTT 协议的 URL 是否正确,例如 `tcp://[hostname]:[port]` 或者 `ssl://[hostname]:[port]`。 - **认证信息缺失**:确保已提供正确的用户名和密码。 ### 2. 检查网络连接 验证您的计算机可以访问 EMQX 的服务器地址。您可以使用 `ping` 命令或其他网络测试工具检查连通性。如果网络有防火墙规则限制,需要确保 EMQX 服务端口被开放。 #### 相关问题 - 网络问题示例: - **防火墙设置**:EMQX 服务使用的端口号是否已被防火墙阻止? - **域名解析**:如果使用了域名,请确保 DNS 解析正常工作。 ### 3. 检查 EMQX 服务器状态 确保 EMQX 服务器正在运行并且状态良好。可以登录到 EMQX 控制面板检查当前连接情况和服务状态。 #### 相关问题 - 服务器状态示例: - **服务启动状态**:使用命令行检查 EMQX 是否已经启动并监听指定端口。 - **日志查看**:查找关于拒绝连接的错误日志条目,它们通常包含详细的失败原因。 ### 4. 查看 Java 客户端异常信息 如果尝试连接时遇到了错误,确保捕获并详细记录异常信息。这将帮助您理解具体的失败原因,比如超时、认证失败等。 #### 相关问题 - 异常处理示例: - **异常信息分析**:异常堆栈跟踪中是否有任何有关连接失败的信息? ### 5. 检查版本兼容性 确保 Java 版本和 EMQX 的库版本相匹配。有时特定的库版本可能会存在兼容性问题。 #### 相关问题 - 兼容性问题示例: - **依赖版本**:检查 Java 应用程序使用的 MQTT 客户端库是否与 EMQX 实现的协议版本兼容。 解决这些问题通常需要逐步排查并结合相应的调试工具进行操作。如果您仍然无法解决问题,建议查阅官方文档或寻求社区支持,特别是针对特定版本的 EMQXJava MQTT 客户端。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值