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; # 关键配置,确保域名正确传递 }




