物联网实战|spring-boot4 + hiveMQ|集成tls

源码如下(mqtt-simulator-hivemq-01)

https://gitee.com/kcnf-iot/mqtt-simulator/tree/master/mqtt-simulator-hivemq-01

SAN是什么

  • Subject DN(主体名称)

旧标准里,证书域名写在 CN(Common Name,通用名)字段,只能填1 个域名
例:CN=shturl.cc/Y,只能匹配这一个网址
  • SAN(主题备用名称)

是 X.509 证书标准扩展字段,可以填写多个域名、IP、主机名,支持:
多个独立域名:a.com、b.com
泛域名:*.shturl.
IP 地址:192.168.1.1、IPv6
本地主机名、邮箱等

问题点

  • ca证书转换jks
  • spring-boot4开启san强校验问题

ca证书生成

window系统安装openssl

https://slproweb.com/products/Win32OpenSSL.html

将下面命名文件server.cnf,生成ca证书(包含subjectAltName-san)

[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no

[req_distinguished_name]
CN = emqx.local

[v3_req]
keyUsage = keyEncipherment, dataEncipherment, digitalSignature
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
IP.1 = 127.0.0.1
IP.2 = 192.168.0.20

生成CA私钥(进入openssl目录bin下)

openssl genrsa -out server.key 2048

生成CA证书

openssl req -x509 -new -nodes -key server.key -sha256 -days 3650 -out server.crt -config server.cnf

生成结果

img

ca证书转换jks

借助keytools(进入jdk目录bin下),将server.crt拷贝过来

keytool -importcert -alias server -file server.crt -keystore truststore.jks -storepass 123456 -noprompt

img

spring-boot4 + hiveMQ集成

pom依赖

<dependencyManagement>
    <dependencies>
        <!-- Spring Boot Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>4.0.6</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<!-- HiveMQ MQTT5 客户端(同时兼容 MQTT3.1.1) -->
<dependency>
    <groupId>com.hivemq</groupId>
    <artifactId>hivemq-mqtt-client</artifactId>
    <version>1.3.15</version>
</dependency>

代码如下

package com.jysemel.iot;

import com.hivemq.client.mqtt.MqttClientSslConfig;
import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient;
import com.hivemq.client.mqtt.mqtt5.Mqtt5Client;
import com.hivemq.client.mqtt.mqtt5.message.auth.Mqtt5SimpleAuth;
import com.hivemq.client.mqtt.mqtt5.message.connect.Mqtt5Connect;

import javax.net.ssl.TrustManagerFactory;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.UUID;

public class HiveMQTLSClientExample {

    private static final String MQTT_HOST = "192.168.0.20";
    private static final int MQTT_TLS_PORT = 8085;
    private static final String MQTT_USER = "test";
    private static final String MQTT_PWD = "123456";
    private static final int KEEP_ALIVE = 30;

    public static void main(String[] args)  {
        mqtt5AsyncClient();
    }

    public static Mqtt5AsyncClient mqtt5AsyncClient() {
        // 1. 加载CA根证书,构建SSL信任配置
        MqttClientSslConfig sslConfig = buildSslConfig();
        // 2. 构建MQTT TLS客户端
        String clientId = "server_" + UUID.randomUUID().toString().substring(0, 16);
        Mqtt5AsyncClient client = Mqtt5Client.builder()
                .identifier(clientId)
                .serverHost(MQTT_HOST)
                .serverPort(MQTT_TLS_PORT)
                .sslConfig(sslConfig)
                .buildAsync();
        // 3. 构建连接报文
        Mqtt5SimpleAuth simpleAuth = Mqtt5SimpleAuth.builder()
                .username(MQTT_USER)
                .password(MQTT_PWD.getBytes())
                .build();
        Mqtt5Connect connectMsg = Mqtt5Connect.builder()
                .cleanStart(true)
                .simpleAuth(simpleAuth)
                .keepAlive(KEEP_ALIVE)
                .build();
        // 4. 发起异步连接
        client.connect(connectMsg)
                .whenComplete((connAck, throwable) -> {
                    if (throwable != null) {
                        throwable.printStackTrace();
                        System.out.println("MQTT TLS连接失败 ");
                        return;
                    }
                    System.out.println("MQTT TLS连接成功,clientId ");
                });
        return client;
    }

    private static MqttClientSslConfig buildSslConfig() {
        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            try (InputStream is = HiveMQTLSClientExample.class.getResourceAsStream("/cert/emqx.jks")) {
                if (is == null) {
                    throw new RuntimeException("信任库文件未找到: /cert/emqx.jks");
                }
                String trustStorePassword = "123456";
                trustStore.load(is, trustStorePassword.toCharArray());
            }

            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(trustStore);

            return MqttClientSslConfig.builder()
                    .trustManagerFactory(tmf)
                    // 关键:自定义 HostnameVerifier,总是验证通过(仅测试用!)
                    .hostnameVerifier((hostname, session) -> true)
                    .build();
        } catch (Exception e) {
            throw new RuntimeException("SSL 初始化失败", e);
        }
    }
}

结果如下

img

如果ca缺失san证书,基于spring-boot4 下面错误

avax.net.ssl.SSLHandshakeException: No subject alternative names present
com.hivemq.client.mqtt.exceptions.ConnectionFailedException: javax.net.ssl.SSLHandshakeException: No subject alternative names present
Caused by: javax.net.ssl.SSLHandshakeException: No subject alternative names present
	at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:130)
	at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:383)
	at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:326)
	at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:321)
	at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.check

更多推荐