
本文详解 jetty 11 下构建单端口 https + wss 服务的完整实践,涵盖 ssl 上下文集成、连接器统一配置、websocket servlet 安全映射及常见握手失败的根因排查。
本文详解 jetty 11 下构建单端口 https + wss 服务的完整实践,涵盖 ssl 上下文集成、连接器统一配置、websocket servlet 安全映射及常见握手失败的根因排查。
Jetty 11 对 WebSocket 和 TLS 的支持已全面重构,不再兼容 Jetty 9/10 的旧式 XML 配置惯用法,而是强调编程式连接器编排与协议分层解耦。你遇到的 “SSL handshake error” 和端口分离问题,本质源于两个关键误配:一是 new Server(port_) 自动创建了默认 HTTP 连接器(占用随机或指定端口),二是未将 SSL 连接器与 WebSocket Servlet 正确绑定在同一安全通道上。以下为可直接落地的 Jetty 11 WSS 配置方案。
✅ 正确配置单端口 HTTPS/WSS 服务(推荐方式)
核心原则:仅启用一个 SSL/TLS 连接器,同时承载 HTTP 资源服务与 WebSocket 升级请求。Jetty 默认支持 Upgrade 机制,即客户端通过 HTTPS 发起 GET /ws 请求并携带 Upgrade: websocket 头,Jetty 自动将其升级为 WSS 连接,无需额外端口。
public class SecureWebSocketServer {
private final Server server;
private final int port;
public SecureWebSocketServer(int port, SSLContext sslContext) {
this.port = port;
this.server = new Server(); // ❌ 不要传入 port!避免自动创建 HTTP 连接器
// 1. 配置 SSL 上下文工厂(Jetty 11 推荐使用 SslContextFactory.Server)
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setSslContext(sslContext);
// ⚠️ 关键安全加固(生产环境必加)
sslContextFactory.setIncludeProtocols("TLSv1.2", "TLSv1.3");
sslContextFactory.setExcludeCipherSuites(".*_(EXPORT|NULL|ANON|MD5|SHA1|SHA256|DSS|RC4|DES|IDEA|SEED|CAMELLIA).*");
// 2. 配置 HTTPS 协议栈:SSL → HTTP → WebSocket 升级链
HttpConfiguration httpsConfig = new HttpConfiguration();
httpsConfig.addCustomizer(new SecureRequestCustomizer()); // 启用安全头(如 HSTS)
// 3. 构建单连接器:SSL + HTTP + WebSocket 升级能力
ServerConnector sslConnector = new ServerConnector(
server,
new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
new HttpConnectionFactory(httpsConfig)
);
sslConnector.setPort(port); // ✅ 显式设置端口(如 8443)
server.addConnector(sslConnector);
// 4. 配置 Servlet 上下文(同时服务静态页 + WebSocket)
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
// 注册 WebSocket Servlet(支持 /ws 路径)
JettyWebSocketServlet wsServlet = new JettyWebSocketServlet() {
@Override
protected void configure(JettyWebSocketServletFactory factory) {
factory.addMapping("/ws", (req, resp) -> new EchoWebSocketHandler());
// 可选:添加健康检查等普通 HTTP 端点
factory.addMapping("/health", (req, resp) -> new HealthHandler());
}
};
context.addServlet(new ServletHolder("ws", wsServlet), "/ws");
// 可选:提供前端 HTML 页面(需 HTTPS 加载 wss://)
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setResourceBase("src/main/resources/static"); // 存放 index.html
context.setHandler(new HandlerList(resourceHandler, context));
server.setHandler(context);
}
public void start() throws Exception {
server.start();
System.out.printf("✅ Secure WebSocket server started on https://localhost:%d and wss://localhost:%d/ws%n", port, port);
server.join();
}
// 示例 WebSocket 处理器(回显)
public static class EchoWebSocketHandler implements WebSocketBehavior {
@Override
public void onWebSocketConnect(Session session) {
System.out.println("Client connected: " + session.getRemoteAddress());
}
@Override
public void onWebSocketText(Session session, String message) {
try {
session.getRemote().sendString("Echo: " + message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
? SSLContext 初始化示例(JKS 格式证书)
确保你的 SSLContext 正确加载了密钥库(如 keystore.p12):
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
private static SSLContext createSslContext() throws Exception {
char[] password = "changeit".toCharArray();
KeyStore keyStore = KeyStore.getInstance("PKCS12");
try (InputStream ksStream = Files.newInputStream(Paths.get("etc/keystore.p12"))) {
keyStore.load(ksStream, password);
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, password);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
return sslContext;
}
? 证书生成提示(开发测试):
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes openssl pkcs12 -export -in cert.pem -inkey key.pem -out keystore.p12 -name jetty -CAfile cert.pem -caname root
? 常见错误与修复指南
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
SSL handshake error(客户端) |
客户端信任库不包含服务端证书;或 Jetty 未正确加载私钥 | 使用 openssl s_client -connect localhost:8443 -servername localhost 测试握手;确认 keystore.p12 中同时含 PrivateKeyEntry 和 trustedCertEntry
|
WebSocket 连接被拒绝(wss:// 404) |
前端 JS 使用 wss://host:port/(无路径),但后端只映射 /ws
|
前端必须使用匹配路径:new WebSocket("wss://localhost:8443/ws")
|
浏览器控制台报 Mixed Content
|
前端页面通过 HTTP 加载,却尝试建立 WSS 连接 | 确保前端 HTML 也通过 HTTPS 提供(如 https://localhost:8443/index.html) |
| Jetty 启动后出现两个连接器日志 |
new Server(port) 创建了默认 HTTP 连接器 |
✅ 严格使用 new Server()(无参构造),所有连接器由代码显式 addConnector()
|
✅ 最终验证步骤
- 启动服务:
java -jar your-app.jar→ 日志应仅显示 一个Started ServerConnector{SSL, ...} - 访问
https://localhost:8443→ 确认页面可加载(无证书警告则说明证书可信) - 打开浏览器开发者工具 → Console 执行:
const ws = new WebSocket("wss://localhost:8443/ws"); ws.onopen = () => console.log("✅ WSS connected"); ws.onmessage = e => console.log("Received:", e.data); ws.send("Hello from browser!"); - 检查服务端控制台输出是否打印
Echo: Hello from browser!
✅ 总结:Jetty 11 的 WSS 安全部署 = 单 SSL 连接器 + HTTP 配置链 + WebSocket 映射路径显式声明 + 前端 HTTPS/WSS 协议一致性。摒弃多端口思维,拥抱现代 Web 的“HTTPS 统一入口 + 协议升级”范式,即可零配置实现安全、简洁、符合浏览器标准的实时通信服务。










