如何使用Jetty ProxyServlet处理SSL的CONNECT请求
我来帮你捋捋嵌入式Jetty的AsyncMiddlemanServlet在HTTPS环境下踩的坑,毕竟我之前也折腾过类似的场景😉
1. 先把HTTPS端点的基础配置拉满
很多时候HTTPS代理卡壳,根源是Jetty本身的SSL上下文没配置对。你得给Server或者Connector绑定正确的SslContextFactory,不管用自签证书还是内部CA签发的证书都不能偷懒:
// 配置服务器端SSL上下文 SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); sslContextFactory.setKeyStorePath("/path/to/your/keystore.jks"); sslContextFactory.setKeyStorePassword("your-keystore-pass"); // 重点:如果后端HTTPS服务用的是自签/内部CA证书,必须把它加到信任库 sslContextFactory.setTrustStorePath("/path/to/your/truststore.jks"); sslContextFactory.setTrustStorePassword("your-truststore-pass"); // 把SSL上下文绑定到HTTPS连接器 ServerConnector sslConnector = new ServerConnector( server, new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), new HttpConnectionFactory(new HttpConfiguration()) ); sslConnector.setPort(443); server.addConnector(sslConnector);
要是后端证书没被信任,Jetty连握手都通不过,更别说代理了。
2. 给AsyncMiddlemanServlet的内部HttpClient补SSL配置
AsyncMiddlemanServlet是靠内部HttpClient去请求后端服务的,很多人只配置了服务器端的SSL,忘了给这个客户端也配,结果后端HTTPS请求直接失败。你可以重写newHttpClient()方法补全:
@Override protected HttpClient newHttpClient() throws Exception { HttpClient httpClient = super.newHttpClient(); // 配置客户端SSL上下文,和服务器端信任库保持一致就行 SslContextFactory.Client clientSslContext = new SslContextFactory.Client(); clientSslContext.setTrustStorePath("/path/to/your/truststore.jks"); clientSslContext.setTrustStorePassword("your-truststore-pass"); httpClient.setSslContextFactory(clientSslContext); return httpClient; }
这一步是HTTPS代理能正常转发请求的核心,别漏了!
3. 修正HTTPS转发的逻辑细节
当代理收到HTTPS请求时,要确保转发给后端的也是HTTPS协议,别不小心转成HTTP了。可以重写rewriteTarget()方法做校验:
@Override protected String rewriteTarget(HttpServletRequest request) { String originalTarget = super.rewriteTarget(request); // 根据你的实际场景调整,比如把HTTP目标强制转成HTTPS if (originalTarget.startsWith("http://")) { return originalTarget.replaceFirst("http://", "https://"); } return originalTarget; }
另外记得给后端传递X-Forwarded-Proto: https头部,避免后端服务因为识别不到原始协议而跳转到HTTP。
4. 开DEBUG日志抓问题
要是还是卡壳,直接开Jetty的DEBUG日志,重点看SSL握手和HttpClient请求的细节:
// 代码里临时开DEBUG日志 org.eclipse.jetty.util.log.Log.getRootLogger().setLevel(Level.DEBUG);
或者启动时加参数:-Dorg.eclipse.jetty.LEVEL=DEBUG
日志里能直接看到是SSL握手失败(比如证书不被信任)、URL转发错误,还是异步处理超时,比瞎猜高效多了。
5. 适配老旧服务器的兼容性
你提到是给老旧服务器做代理,那得考虑后端HTTPS服务的SSL协议版本和加密套件是否和Jetty兼容。Jetty 9.4默认禁用了TLSv1.0这类旧协议,如果后端只支持旧协议,得手动开启:
sslContextFactory.setIncludeProtocols("TLSv1.0", "TLSv1.1", "TLSv1.2"); // 按需添加后端支持的加密套件 sslContextFactory.setIncludeCipherSuites("SSL_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA");
这一步要权衡安全性,毕竟旧协议有漏洞,但内部环境下可以临时妥协。
内容的提问来源于stack exchange,提问作者Chris Bartlett

