You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java运行时兼容Let's Encrypt DST Root CA X3证书的WebSocket连接问题

问题:WebSocket服务器Java客户端连接失败,浏览器正常

现象

  • 浏览器可正常通过WebSocket(wss)连接服务器,能收到101状态码并正常通信
  • Java客户端(基于libGDX框架)连接时抛出PKIX路径构建错误

错误信息

执行以下代码时:

WebSocket socket = WebSockets.newSocket(WebSockets.toSecureWebSocketUrl("myDomain", 8080));

抛出错误:

Failed to send an opening handshake request to the server: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

已确认点

  • 客户端主机和端口正确:服务器未启动时会抛出“connection refused”错误
  • 服务器使用certbot生成的证书,SSL测试评级为A
  • 重新签发证书后,仍使用已过期(2021年9月)的DST Root CA X3根证书

需求

  1. 确认Java报错是否与DST Root CA X3过期有关
  2. 实现App在运行时(包括Play Store下载后的版本)自动信任服务器的SSL证书,无需因证书更新重新发布App

Java客户端代码(libGDX/Android)

package ...textwebsockets;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.utils.ScreenUtils;
import com.github.czyzby.websocket.WebSocket;
import com.github.czyzby.websocket.WebSocketListener;
import com.github.czyzby.websocket.WebSockets;

import java.io.ByteArrayInputStream;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.security.auth.x500.X500Principal;

public class TestWebSockets extends ApplicationAdapter {
    public final String LOG_TAG = "LOG_TestWebSockets";

    SpriteBatch batch;
    Texture img;

    @Override
    public void create () {
        batch = new SpriteBatch();
        img = new Texture("badlogic.jpg");
        
        // 使用gdx-websockets库
        WebSocket socket = WebSockets.newSocket(WebSockets.toSecureWebSocketUrl("myDomain", 8080));
        socket.setSendGracefully(true);
        socket.addListener(new WebSocketListener() {
            @Override
            public boolean onOpen(WebSocket webSocket) { ... }

            @Override
            public boolean onClose(WebSocket webSocket, int closeCode, String reason) { ... }

            @Override
            public boolean onMessage(WebSocket webSocket, String packet) { ... }

            @Override
            public boolean onMessage(WebSocket webSocket, byte[] packet) { ... }

            @Override
            public boolean onError(WebSocket webSocket, Throwable error) {
                Gdx.app.log(LOG_TAG, "onError: " + error.getCause().getMessage() + "; " + error.getMessage());
                return false;
            }
        });
        
        socket.connect();
    }

    // 省略其他生命周期方法
}

Node.js服务端代码

'use strict';

const express = require('express');
const path = require('path');
const https = require('https');
const fs = require('fs');
const WebSocket = require('ws');

const app = express();
app.use(express.static(path.join(__dirname, '/public')));

const server = https.createServer({
    key: fs.readFileSync('./privkey.pem'),
    cert: fs.readFileSync('./cert.pem')
}, app);
const wss = new WebSocket.Server({server: server});

wss.on('connection', function (ws) { ... });

server.listen(8080, function () {
    console.log('Listening on 8080');
});

// 省略其他wss事件处理方法

网页端代码

<script>
    const wss = new WebSocket('wss://myDomain:8080');
    // 省略其他wss事件处理方法
</script>

回答

1. Java报错与DST Root CA X3过期的关系

是的,两者直接相关。Java的默认信任库(cacerts)包含DST Root CA X3,但该根证书2021年9月过期后,旧版Java(尤其是Java 8及更早版本)会拒绝信任由其交叉签名的Let's Encrypt证书。浏览器能正常连接是因为多数浏览器已移除对该过期根证书的依赖,转而信任Let's Encrypt的ISRG Root X1根证书。

2. 实现App自动信任证书且无需更新App的方案

方案一:修复服务器证书链

Certbot生成的fullchain.pem包含服务器证书+中间证书的完整链,服务端代码应使用这个文件而非单独的cert.pem:

const server = https.createServer({
    key: fs.readFileSync('./privkey.pem'),
    cert: fs.readFileSync('./fullchain.pem') // 替换为fullchain.pem
}, app);

完整的证书链能帮助旧版Java客户端找到有效的信任路径,即使DST Root CA X3过期,只要客户端信任ISRG Root X1,就能验证通过。

方案二:适配Java/Android信任库

  • Android端:将App的编译SDK和目标SDK升级到Android 11+,新版本系统已默认包含ISRG Root X1根证书,会自动信任Let's Encrypt的新证书链。
  • Java桌面端:升级到Java 11+,或手动更新Java 8的cacerts信任库,添加ISRG Root X1证书。

方案三:自定义SSL信任管理器(Android端推荐)

通过自定义信任管理器,让App仅信任Let's Encrypt的合法证书链,避免完全禁用SSL验证带来的安全风险:

  1. 提前将ISRG Root X1根证书嵌入App资源文件
  2. 自定义信任管理器验证服务器证书链
  3. 为gdx-websockets配置自定义SSL上下文

示例代码片段:

// 初始化自定义SSL上下文
private SSLContext createSslContext() throws Exception {
    // 加载嵌入的ISRG Root X1证书
    InputStream certInputStream = Gdx.files.internal("isrgrootx1.pem").read();
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    X509Certificate trustedCert = (X509Certificate) cf.generateCertificate(certInputStream);

    // 创建信任库并添加证书
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(null, null);
    keyStore.setCertificateEntry("isrgrootx1", trustedCert);

    // 初始化信任管理器
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(keyStore);

    // 创建SSL上下文
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
    return sslContext;
}

// 创建WebSocket时配置自定义SSL上下文
WebSocket socket = WebSockets.newSecureSocket(WebSockets.toWebSocketUrl("myDomain", 8080), createSslContext());

内容的提问来源于stack exchange,提问作者user2342558

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 22:07:03