
本文详解如何在 jetty 11 中正确配置单端口 https/wss 服务,解决 ssl 握手失败、http/https 端口分离、websocket 未启用 tls 等典型问题,并提供可直接运行的生产级代码示例。
本文详解如何在 jetty 11 中正确配置单端口 https/wss 服务,解决 ssl 握手失败、http/https 端口分离、websocket 未启用 tls 等典型问题,并提供可直接运行的生产级代码示例。
在 Jetty 11 中搭建安全的 WebSocket 服务(即 wss:// 协议),关键在于统一 TLS 层、复用单一 HTTPS 连接器,并确保 WebSocket 升级请求经由加密通道处理。你遇到的 “SSL handshake error” 和端口分离问题,根源在于错误地初始化了两个独立连接器(一个纯 HTTP,一个 SSL),而 Jetty 的 WebSocket Servlet 默认仅绑定到 HTTP 连接器——它无法自动感知或接管 SSL 连接器上的流量。
✅ 正确架构:单端口 HTTPS + WSS 复用
Jetty 11 不再推荐为 HTTP 和 HTTPS 分别创建连接器。正确的做法是:
-
仅创建一个
ServerConnector,使用SslConnectionFactory+HttpConnectionFactory组合; -
所有流量(包括
/的静态资源、/ws的 WebSocket 升级请求)均走同一 HTTPS 端口; - WebSocket 升级握手(Upgrade: websocket)天然兼容 HTTPS,无需额外协议桥接。
⚠️ 注意:
JettyWebSocketServlet本身不处理 TLS;TLS 由SslConnectionFactory在连接层完成。只要 WebSocket 端点注册在 HTTPS 连接器上,浏览器发起wss://host:port/ws请求时,整个握手和后续帧传输均自动加密。
WebSocket 8.18.2下载WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
? 修正后的完整配置代码(Jetty 11.0.15+)
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory;
public class SecureWebSocketServer {
private final Server server;
private final int port;
public SecureWebSocketServer(int port, String keyStorePath, String keyStorePassword) {
this.port = port;
this.server = new Server(); // ❌ 不传 port!避免隐式创建默认 HTTP 连接器
// 1. 配置 SSL 上下文工厂(推荐使用 PKCS12,兼容性更好)
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keyStorePath);
sslContextFactory.setKeyStorePassword(keyStorePassword);
sslContextFactory.setKeyManagerPassword(keyStorePassword); // 若密钥密码不同,请单独设置
// 2. 配置 HTTPS 连接器(单端口,同时支持 HTTP/1.1 和 WebSocket Upgrade)
HttpConfiguration httpsConfig = new HttpConfiguration();
httpsConfig.addCustomizer(new SecureRequestCustomizer()); // 启用安全头(如 HSTS)
ServerConnector httpsConnector = new ServerConnector(
server,
new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
new HttpConnectionFactory(httpsConfig)
);
httpsConnector.setPort(port); // ✅ 显式设置端口
httpsConnector.setIdleTimeout(30_000);
server.addConnector(httpsConnector);
// 3. 注册 WebSocket Servlet(路径 "/" 或 "/ws" 均可,客户端用 wss://host:port/ws)
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
JettyWebSocketServlet wsServlet = new JettyWebSocketServlet() {
@Override
protected void configure(JettyWebSocketServletFactory factory) {
factory.addMapping("/ws", (req, res) -> new EchoWebSocketHandler());
// 可选:添加健康检查或静态页面支持
factory.getServletContext().addServlet("default", "org.eclipse.jetty.servlet.DefaultServlet")
.addMapping("/");
}
};
context.addServlet(new ServletHolder(wsServlet), "/ws");
server.setHandler(context);
}
public void start() throws Exception {
server.start();
System.out.printf("✅ Secure WebSocket server started on wss://localhost:%d/ws%n", port);
server.join();
}
// 示例 WebSocket 处理器(回显)
public static class EchoWebSocketHandler extends WebSocketHandler {
@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 (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
// 替换为你的证书路径(建议使用 PKCS12 格式:keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.p12 -deststoretype pkcs12)
String keystore = "src/main/resources/keystore.p12";
String password = "changeit";
new SecureWebSocketServer(8443, keystore, password).start();
}
}? 关键配置说明与避坑指南
| 项目 | 正确做法 | 错误做法 | 后果 |
|---|---|---|---|
| Server 初始化 |
new Server()(无参) |
new Server(port) |
后者会隐式创建一个非 SSL 的 HTTP 连接器,导致端口冲突和流量分流 |
| 证书格式 | 优先使用 .p12(PKCS12),设置 setKeyStoreType("PKCS12")
|
仅用 JKS 且未设类型 | Jetty 11 默认尝试 PKCS12,JKS 需显式声明,否则加载失败 |
| WebSocket 路径 | 注册在 /ws,客户端访问 wss://host:8443/ws
|
注册在 / 并期望 wss://host:8443/
|
部分浏览器对根路径升级支持不稳定,推荐明确子路径 |
| SSL 协议控制 | 在 SslContextFactory 中禁用弱协议:sslContextFactory.setExcludeProtocols("SSL", "SSLv2", "SSLv3");
|
依赖默认值 | 避免 TLS 1.0/1.1 等已被弃用协议,满足 PCI DSS 和现代浏览器要求 |
| 客户端测试 | 使用浏览器 wss://localhost:8443/ws 或 wscat -c wss://localhost:8443/ws
|
尝试 ws:// 连接 HTTPS 端口 |
协议不匹配,立即断连 |
?️ 安全加固建议(生产环境必备)
-
证书验证:确保证书由可信 CA 签发(或自签名证书已导入客户端信任库),避免
NET::ERR_CERT_AUTHORITY_INVALID; -
CORS 控制:在
JettyWebSocketServletFactory中设置允许源:factory.getServletContext().setAttribute("org.eclipse.jetty.websocket.server.WebSocketCreator", (HttpServletRequest req, HttpServletResponse resp) -> { String origin = req.getHeader("Origin"); if ("https://trusted-domain.com".equals(origin)) { resp.setHeader("Access-Control-Allow-Origin", origin); } return new EchoWebSocketHandler(); }); -
会话超时:调用
session.setIdleTimeout(60_000)防止长连接耗尽资源; -
日志监控:启用 Jetty 访问日志并过滤
WSS UPGRADE请求,快速定位握手失败原因。
✅ 验证是否成功
启动服务后,执行以下任一验证:
- 浏览器打开
https://localhost:8443/(应显示 404,证明 HTTPS 工作); - 在浏览器控制台运行:
const ws = new WebSocket("wss://localhost:8443/ws"); ws.onopen = () => console.log("✅ WSS connected"); ws.onmessage = e => console.log("←", e.data); ws.send("Hello from browser!"); - 使用命令行工具:
wscat -c wss://localhost:8443/ws --no-check # 自签名证书需加 --no-check
? 提示:若使用自签名证书,Chrome/Firefox 会阻止未手动信任的
wss://连接。开发阶段可先访问https://localhost:8443并点击“高级 → 继续前往”,使证书被临时信任。
通过以上配置,你将获得一个符合现代安全标准(TLS 1.2+、强密码套件、单端口复用)的 Jetty 11 WSS 服务,彻底规避握手失败与端口混乱问题,为实时通信筑牢加密基石。


















