# 背景介绍
在一个项目中,我们使用了其他同事提供的 websocket 代码。项目接近测试结束阶段时,我对代码进行了审查,发现 IDE 提示当前使用的 websocket 版本存在 CVE 漏洞。起初认为这可能只是一个小问题,只需要简单地升级版本就能解决。然而,事实证明这个过程比预想的要复杂得多。
# 使用的开源组件:org.java-websocket
原始依赖版本是 1.3.0:
<dependency> | |
<groupId>org.java-websocket</groupId> | |
<artifactId>Java-WebSocket</artifactId> | |
<version>1.3.0</version> | |
</dependency> |
# 升级过程中遇到的主要问题
当我尝试将版本直接升级到最新的 1.5.6 时,遇到了两个主要问题:
- API 发生变化
- 信任所有证书的方法报错
# 1. API 变化
升级前后的代码对比:
// 升级前 | |
MyWebsocketClient client = new MyWebsocketClient(new URI(uri), new Draft_76(), new HashMap<>(), 2000); | |
SSLContext sc = SSLContext.getInstance("TLS"); | |
sc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); | |
client.setWebSocketFactory(new DefaultSSLWebSocketServerFactory(sc)); | |
// 升级后 | |
MyWebsocketClient client = new MyWebsocketClient(new URI(uri), new Draft_6455(), new HashMap<>(), 2000); | |
SSLContext sc = SSLContext.getInstance("TLS"); | |
sc.init(null, trustAllCerts, new java.security.SecureRandom()); | |
client.setSocketFactory(sc.getSocketFactory()); |
主要变化:
setWebSocketFactory()
方法变为setSocketFactory()
Draft_76
更新为Draft_6455
通过查看 GitHub 上的官方示例和 git 记录,我发现 API 经历了以下变化:
chatclient.setWebSocketFactory(new SSLWebSocketClientFactory(sslContext));
chatclient.setSocket(factory.createSocket());
chatclient.setSocketFactory(factory);
# 2. 信任所有证书报错
升级后,日志中出现了证书校验的报错。通过源码分析,发现问题出在 chooseTrustManager
方法:
private X509TrustManager chooseTrustManager(TrustManager[] tm) { | |
// 代码省略... | |
return new AbstractTrustManagerWrapper((X509TrustManager)tm[i]); | |
} |
这个方法返回的 AbstractTrustManagerWrapper
包含了证书校验逻辑,而不是使用我们自定义的 X509TrustManager
。
# 解决方案
为了解决这个问题,我们需要实现 X509ExtendedTrustManager
接口。以下是修改后的代码:
public Optional<MyWebsocketClient> generateWebsocketClient(String uri) throws URISyntaxException { | |
MyWebsocketClient client = new MyWebsocketClient(new URI(uri), new Draft_6455(), new HashMap<>(), 2000); | |
TrustManager[] trustAllCerts = new TrustManager[]{ | |
new X509ExtendedTrustManager() { | |
@Override | |
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } | |
@Override | |
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } | |
@Override | |
public X509Certificate[] getAcceptedIssuers() { | |
return new X509Certificate[0]; | |
} | |
@Override | |
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { } | |
@Override | |
public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { } | |
@Override | |
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { } | |
@Override | |
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { } | |
} | |
}; | |
try { | |
SSLContext sc = SSLContext.getInstance("SSL"); | |
sc.init(null, trustAllCerts, new java.security.SecureRandom()); | |
client.setSocketFactory(sc.getSocketFactory()); | |
} catch (NoSuchAlgorithmException | KeyManagementException e) { | |
log.error(e.getMessage(), e); | |
} | |
boolean connected = false; | |
try { | |
connected = client.connectBlocking(); | |
} catch (InterruptedException e) { | |
log.error("connectBlocking fail", e); | |
} | |
if (!connected) { | |
client.close(); | |
return Optional.empty(); | |
} | |
return Optional.of(client); | |
} |
# 总结
- 升级第三方依赖时,要仔细查看更新日志和 API 变化。
- 对于安全相关的更改,如 SSL/TLS 配置,需要特别注意。
- 在升级过程中,逐步测试每个变更是很重要的。
- 保持与开源社区的最新动态同步,可以帮助我们更好地理解和应对 API 变化。