Android HiveMQ客户端连接test.mosquitto.org 8885端口SSL握手失败
解决Android端HiveMQ连接test.mosquitto.org 8885端口的SSL握手错误(证书无匹配subjectAltNames)
问题原因
test.mosquitto.org的8885端口使用的是无Subject Alternative Name(SAN)字段的自签名证书。从Android 7.0开始,系统默认的Conscrypt安全提供者会强制要求SSL证书包含与服务器域名匹配的SAN字段——即使证书已被信任,缺少SAN也会触发CertificateException: 证书上无匹配的subjectAltNames错误。而8883/8886端口使用的是正规CA签发、带合法SAN的证书,因此能正常连接。
解决方案
方案1:测试环境临时绕过SAN校验(仅用于测试,禁止生产环境使用)
通过自定义X509TrustManager跳过SAN字段的校验逻辑,具体实现如下:
- 实现自定义TrustManager:
import javax.net.ssl.X509TrustManager; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; public class NoSanCheckTrustManager implements X509TrustManager { private final X509TrustManager originalTrustManager; public NoSanCheckTrustManager(X509TrustManager original) { this.originalTrustManager = original; } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { originalTrustManager.checkClientTrusted(chain, authType); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { try { // 先尝试默认校验,失败则针对性跳过SAN检查 originalTrustManager.checkServerTrusted(chain, authType); } catch (CertificateException e) { // 仅捕获SAN不匹配的异常,其他异常仍正常抛出 if (e.getMessage() != null && e.getMessage().contains("subjectAltNames")) { return; } throw e; } } @Override public X509Certificate[] getAcceptedIssuers() { return originalTrustManager.getAcceptedIssuers(); } }
- 配置HiveMQ客户端使用自定义TrustManager:
import javax.net.ssl.TrustManagerFactory; import java.security.KeyStore; import java.util.UUID; import com.hivemq.client.mqtt.MqttClient; import com.hivemq.client.mqtt.MqttClientSslConfig; import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient; // 获取系统默认TrustManagerFactory TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init((KeyStore) null); // 替换为自定义TrustManager X509TrustManager originalTm = (X509TrustManager) tmf.getTrustManagers()[0]; X509TrustManager customTm = new NoSanCheckTrustManager(originalTm); // 构建自定义SSL配置 MqttClientSslConfig sslConfig = MqttClientSslConfig.builder() .trustManagerFactory(new TrustManagerFactory() { @Override protected void engineInit(KeyStore keyStore) throws Exception {} @Override protected void engineInit(ManagerFactoryParameters managerFactoryParameters) throws Exception {} @Override protected javax.net.ssl.TrustManager[] engineGetTrustManagers() { return new javax.net.ssl.TrustManager[]{customTm}; } }) .build(); // 初始化客户端并添加用户名密码 MqttClientBuilder mqttClientBuilder = MqttClient.builder() .identifier(UUID.randomUUID().toString()) .serverHost("test.mosquitto.org") .serverPort(8885) .sslConfig(sslConfig); Mqtt3AsyncClient mqtt3Client = mqttClientBuilder .useMqttVersion3() .buildAsync(); // 发起连接 mqtt3Client.connectWith() .simpleAuth() .username("rw") .password("readwrite".getBytes()) .applySimpleAuth() .send() .subscribe( connectAck -> { // 连接成功处理逻辑 }, throwable -> { // 连接失败处理逻辑 } );
方案2:生产环境安全解决方案
如果你计划将代码用于生产应用,绝对不能使用方案1的绕过逻辑,建议:
- 改用带有合法CA签发证书的MQTT服务器(类似test.mosquitto.org的8883/8886端口)。
- 如果必须使用自签名证书的服务器,务必为证书添加正确的SAN字段(将服务器域名作为DNS条目加入证书),然后将该证书导入到Android应用的信任存储中,确保SSL校验完全合规。
内容的提问来源于stack exchange,提问作者Palakkumar Darji
相关产品推荐
相关产品推荐

