Java Dropwizard后端部署AWS后调用外部API遇SSL握手异常
Java Dropwizard部署AWS后调用外部API出现SSL握手失败
问题详情
我正在开发Java Dropwizard后端应用,需从后端调用外部API(https://example.com)。本地环境(http://localhost:8080/)可正常调用,但部署到AWS服务器(https://deployed.application.example)后,调用时抛出以下错误:
! java.io.EOFException: SSL peer shut down incorrectly ! at java.base/sun.security.ssl.SSLSocketInputRecord.read(Unknown Source) ! at java.base/sun.security.ssl.SSLSocketInputRecord.readHeader(Unknown Source) ! at java.base/sun.security.ssl.SSLSocketInputRecord.decode(Unknown Source) ! at java.base/sun.security.ssl.SSLTransport.decode(Unknown Source) ! ... 95 common frames omitted ! Causing: javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake ! at java.base/sun.security.ssl.SSLSocketImpl.handleEOF(Unknown Source) ! at java.base/sun.security.ssl.SSLSocketImpl.decode(Unknown Source) ! at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(Unknown Source) ! at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source) ! at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source) ! at java.base/sun.net.www.protocol.https.HttpsClient.afterConnect(Unknown Source) ! at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(Unknown Source) ! at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(Unknown Source) ! at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source) ! at java.base/java.net.HttpURLConnection.getResponseCode(Unknown Source) ! at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(Unknown Source) ! at org.glassfish.jersey.client.internal.HttpUrlConnector.handleException(HttpUrlConnector.java:549) ! at org.glassfish.jersey.client.internal.HttpUrlConnector._apply(HttpUrlConnector.java:375) ! at org.glassfish.jersey.client.internal.HttpUrlConnector.apply(HttpUrlConnector.java:267) ! ... 82 common frames omitted ! Causing: javax.ws.rs.ProcessingException: javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake ! at org.glassfish.jersey.client.internal.HttpUrlConnector.apply(HttpUrlConnector.java:269) ! at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:297) ! at org.glassfish.jersey.client.JerseyInvocation.lambda$invoke$0(JerseyInvocation.java:662) ! at org.glassfish.jersey.client.JerseyInvocation.call(JerseyInvocation.java:697)
以下是调用外部API的代码片段,我已尝试禁用主机名验证,但无效:
ClientBuilder clientBuilder = ClientBuilder.newBuilder(); clientBuilder.hostnameVerifier((hostname, session) -> true); // 禁用主机名验证 SSLContext sslContext; try { sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, null, null); // 使用null参数禁用SSL验证 } catch (KeyManagementException | NoSuchAlgorithmException e) { throw new RuntimeException(e); } clientBuilder.sslContext(sslContext); Client client = clientBuilder.build(); // 准备API请求 String apiUrl = API_URL + "/auth"; String httpMethod = "POST"; String requestBody = "{ \"username\": \"username\", \"password\": \"password\" }"; MediaType requestMediaType = MediaType.APPLICATION_JSON_TYPE; MediaType responseMediaType = MediaType.APPLICATION_JSON_TYPE; // 构建API请求 javax.ws.rs.client.Invocation.Builder requestBuilder = client.target(apiUrl) .request(responseMediaType) .header("Accept", "application/json") .header("Content-Type", "application/json");// 添加自定义请求头 // 设置HTTP方法和请求体(如果需要) Response response; if ("POST".equalsIgnoreCase(httpMethod) || "PUT".equalsIgnoreCase(httpMethod)) { response = requestBuilder.method(httpMethod, Entity.entity(requestBody, requestMediaType)); } else { response = requestBuilder.method(httpMethod); } // 处理API响应 int statusCode = response.getStatus(); String responseBody = response.readEntity(String.class); // 清理资源 response.close(); client.close(); // 处理API响应结果 if (statusCode == Response.Status.OK.getStatusCode()) { // 成功!处理响应 } else { // 处理错误响应 System.err.println("API调用失败,状态码: " + statusCode); System.err.println("响应内容: " + responseBody); }
解决方案
1. 排查AWS网络限制
- 检查EC2实例的安全组出站规则,确保允许访问外部API的443端口(HTTPS)
- 确认VPC网络ACL没有阻止443端口的出站流量
- 如果使用NAT网关,验证网关配置正常,能正常访问公网
2. 确认外部API的IP白名单
- 联系外部API服务商,将AWS服务器的公网IP加入他们的访问白名单
- 在AWS服务器上执行
curl https://example.com/auth测试直接访问,排查是否是网络层面的问题
3. 强制指定兼容的TLS版本
外部API可能仅支持TLS 1.2及以上版本,而AWS环境的Java默认配置可能启用了旧版本。可以通过两种方式处理:
- 在代码中指定TLS版本:
SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); // 搭配完整的信任管理器配置(见下文) sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
- 或者在JVM启动参数中添加:
-Dhttps.protocols=TLSv1.2,TLSv1.3
4. 修复SSL证书验证配置
当前代码中sslContext.init(null, null, null)并没有完全禁用证书验证,需要自定义TrustManager:
// 创建信任所有证书的TrustManager TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } public void checkClientTrusted(X509Certificate[] certs, String authType) {} public void checkServerTrusted(X509Certificate[] certs, String authType) {} } }; SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init(null, trustAllCerts, new SecureRandom()); clientBuilder.sslContext(sslContext); clientBuilder.hostnameVerifier((hostname, session) -> true);
5. 配置代理(如果需要)
如果AWS服务器需通过代理访问外部网络,在ClientBuilder中添加代理配置:
clientBuilder.property(ClientProperties.PROXY_URI, "http://your-proxy:port"); // 如果代理需要认证 clientBuilder.property(ClientProperties.PROXY_USERNAME, "proxy-user"); clientBuilder.property(ClientProperties.PROXY_PASSWORD, "proxy-pass");
内容的提问来源于stack exchange,提问作者Jyoti
相关产品推荐
相关产品推荐

