You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Java 8环境下WebSocket通过域名连接时出现SSL证书IP匹配异常(部分用户受影响)

Java 8环境下WebSocket通过域名连接时出现SSL证书IP匹配异常(部分用户受影响)

看起来你遇到了一个挺头疼的Java 8特有的SSL证书验证问题,先帮你理清楚问题根源和解决办法:

你明明用的是域名wss://testserver.heckvision.com连接WebSocket,但部分用户的Java 8客户端却抛出了IP匹配的SSL错误,Java 21环境下完全正常,这确实很迷惑人。先把你提供的错误日志整理一下:

[21:48:31] [pool-4-thread-1/INFO]: [com.heckvision.bingosplash.web.WebSocketManager:lambda$tryStartConnection$0:48]: Attempting to connect to WebSocket... 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:77]: WebSocket error: 
[21:48:32] [WebSocketWriteThread-107/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:77]: WebSocket error: 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative names matching IP address 45.116.104.89 found 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949) 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302) 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296) 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1497) 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:212) 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: at sun.security.ssl.Handshaker.processLoop(Handshaker.java:979) 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: at sun.security.ssl.Handshaker.process_record(Handshaker.java:914) 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1062) 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375) 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:928) 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: at sun.security.ssl.AppInputStream.read(AppInputStream.java:105) 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: at java.io.InputStream.read(InputStream.java:101) 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: at com.heckvision.shadowed.java_websocket.client.WebSocketClient.run(WebSocketClient.java:543) 
[21:48:32] [WebSocketConnectReadThread-106/INFO]: [com.heckvision.bingosplash.web.WebSocketManager$1:onError:78]: at java.lang.Thread.run(Thread.java:745)

问题根源

这个问题本质是Java 8与高版本Java在SSL证书验证逻辑上的差异

  • 高版本Java(如21)的SSL验证逻辑清晰:只要用域名发起请求,就只会校验证书中绑定的域名SAN(Subject Alternative Name)字段,不会匹配解析后的IP。
  • 但Java 8存在特殊验证分支:当部分用户的DNS解析环境异常(比如缓存冲突、解析路径特殊),Java 8的SSL实现会错误地把域名解析后的IP提取出来,和证书的SAN字段做匹配。而你的证书仅绑定了testserver.heckvision.com域名,未将IP45.116.104.89加入SAN,因此触发了验证失败。

75%用户受影响的情况,也和不同用户的DNS、网络环境差异有关,导致Java 8触发了不同的验证路径。

可行的解决办法

1. 给SSL证书添加IP类型的SAN字段(最推荐,彻底解决)

既然问题是证书缺少IP SAN,直接给Let's Encrypt证书添加IP绑定是最彻底的方案:

  • 使用certbot重新申请证书,命令示例:
    certbot certonly --cert-name testserver.heckvision.com -d testserver.heckvision.com --ip-san 45.116.104.89
    
  • 重新部署新证书到Nginx,确保配置文件正确引用了更新后的证书文件。

2. 调整Java 8客户端的SSL验证逻辑(临时测试用)

如果暂时无法修改证书,可以在Java 8客户端代码中自定义SSLContext,跳过IP与SAN的匹配检查(注意:此方法会降低安全性,仅建议测试环境使用):

package com.heckvision.bingosplash.web;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

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

public class CustomWebSocketClient extends WebSocketClient {

    public CustomWebSocketClient(URI serverUri) {
        super(serverUri);
        // 初始化自定义SSLContext
        try {
            TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                    public void checkClientTrusted(X509Certificate[] certs, String authType) {}
                    public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
                        // 仅验证证书有效性,跳过IP SAN匹配
                        certs[0].checkValidity();
                    }
                }
            };
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            this.setSocket(sc.getSocketFactory().createSocket());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onOpen(ServerHandshake handshakedata) {
        // 你的业务逻辑实现
    }

    @Override
    public void onMessage(String message) {
        // 你的业务逻辑实现
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        // 你的业务逻辑实现
    }

    @Override
    public void onError(Exception ex) {
        // 你的业务逻辑实现
    }
}

3. 强制Java 8使用标准域名验证逻辑(尝试方案)

在Java 8客户端启动时添加JVM参数,确保SNI扩展开启(Java 8u25+默认开启,但部分环境可能被关闭):

-Djsse.enableSNIExtension=true

开启SNI后,Java 8的SSL验证会更贴近高版本Java的逻辑,减少错误触发的概率。

额外检查项

确认Nginx的WebSocket转发配置中,正确设置了Host请求头,避免后端服务出现验证异常:

location /ws {
    proxy_pass http://localhost:你的WebSocket端口;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host; # 关键配置,确保域名正确传递
}

火山引擎 最新活动