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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 15:27:11