HTTPS请求报错SSLException: Received fatal alert: internal_error
问题背景
调用API发送HTTPS POST请求时,执行connection.getOutputStream()获取输出流环节触发错误,同一段代码在HTTP协议场景下可正常运行。
代码实现逻辑:初始化URL连接后,根据目标地址协议自动选择HTTP/HTTPS连接通道,配置请求头、超时时间等参数后写入请求体,最后读取响应内容拼接返回。
对应实现代码如下:
public String sendNativePost(String httpURL, Map headers, String body, String respType) throws Exception { URL url = new URL(httpURL); HttpURLConnection connection; if (url.getProtocol().toLowerCase().equals("https")) { url = new URL(null, httpURL, new sun.net.www.protocol.https.Handler()); trustAllHosts(); HttpsURLConnection https = (HttpsURLConnection) url.openConnection(); https.setHostnameVerifier(DO_NOT_VERIFY); connection = https; } else { connection = (HttpURLConnection) url.openConnection(); } connection.setRequestMethod("POST"); connection.setRequestProperty("Accept", "*/*"); if (headers != null) { for (Object key : headers.keySet()) { connection.setRequestProperty((String) key, (String) headers.get(key)); } } else { logger.error("Header is Empty"); } connection.setConnectTimeout(10 * 1000); connection.setReadTimeout(10 * 1000); connection.setDoOutput(true); connection.setDoInput(true); connection.setRequestProperty( "User-Agent", SMSServer.APPLICATION_NAME + "_" + SMSServer.APPLICATION_VERSION); try (OutputStream os = connection.getOutputStream()) { byte[] input = body.getBytes(StandardCharsets.UTF_8); logger.debug("get body-"); os.write(input, 0, input.length); logger.debug("write body"); } catch (Exception e) { logger.error("Output Stream Error!", e); } BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream())); String responseMessage = ""; while (true) { String line = br.readLine(); if (line != null) { responseMessage += line; } else { break; } } br.close(); connection.disconnect(); return responseMessage; }
报错信息
代码执行时抛出SSL异常,日志如下:
2022-06-13 17:17:41:323 ERROR HttpClient:224 - Output Stream Error! javax.net.ssl.SSLException: Received fatal alert: internal_error at sun.security.ssl.Alerts.getSSLException(Alerts.java:208) at sun.security.ssl.Alerts.getSSLException(Alerts.java:154) at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:2023) at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1125) at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387) at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559) at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185) at sun.net.www.protocol.http.HttpURLConnection.getOutputStream0(HttpURLConnection.java:1316) at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1291) at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:250) at smsclient.HTTPClient.sendNativePost(HTTPClient.java:217) at smsclient.SMSClientHttp.sendPOST(SMSClientHttp.java:256) at smsclient.SMSClientHttp.sendSMS(SMSClientHttp.java:147) at smsclient.SMSClientHttp.processSMS(SMSClientHttp.java:42) at smsclient.HTTPClientMessageSender.run(HTTPClientMessageSender.java:75)
问题定位与修复
从报错栈可以看出,异常发生在SSL初始握手阶段,调用getOutputStream()时会隐式触发连接建立和SSL握手,还没执行到写请求体的逻辑,和输出流本身无关,是HTTPS握手被服务端拒绝导致的。
根因
- 代码依赖JDK内部私有API:直接实例化
sun.net.www.protocol.https.Handler、重复初始化URL对象的操作属于非标准用法,不同JDK版本、厂商的内部实现存在差异,很容易导致SSL上下文初始化异常。 - TLS版本不匹配:从报错栈的行号判断当前运行环境是JDK8早期版本,默认启用的TLS版本为1.0/1.1,而当前绝大多数公网HTTPS API已经强制要求TLS1.2及以上版本,客户端发起握手时使用的版本过低,服务端直接返回internal_error断开连接,这是该报错最常见的诱因。
- 自定义信任逻辑存在缺陷:普遍流传的
trustAllHosts()实现大多没有正确初始化SSL上下文,也没有显式指定支持的加密套件,容易导致握手失败。 - 代码存在异常吞掉的问题:输出流获取/写入的异常被catch后仅打日志,没有终止执行,后续还会继续调用
getInputStream(),会抛出二次异常掩盖真实根因。
修复方案
- 移除所有依赖JDK内部私有API的逻辑,不需要重复实例化URL对象,删除
new URL(null, httpURL, new sun.net.www.protocol.https.Handler())这段冗余代码。 - 正确初始化SSL上下文,显式指定使用TLSv1.2版本,测试环境可临时配置信任所有证书跳过校验,生产环境必须将服务端证书导入信任库使用正规校验逻辑:
// 全局初始化一次即可,不需要每次请求都执行 private static void initSSLContext() throws Exception { TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } @Override public void checkClientTrusted(X509Certificate[] certs, String authType) {} @Override public void checkServerTrusted(X509Certificate[] certs, String authType) {} } }; // 强制指定TLS版本为1.2,适配服务端要求 SSLContext sc = SSLContext.getInstance("TLSv1.2"); sc.init(null, trustAllCerts, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); // 以下主机名校验跳过逻辑仅测试环境使用,生产环境需移除 HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); }
- 修正HTTPS连接分支逻辑:
URL url = new URL(httpURL); HttpURLConnection connection; if (url.getProtocol().toLowerCase().equals("https")) { initSSLContext(); connection = (HttpsURLConnection) url.openConnection(); } else { connection = (HttpURLConnection) url.openConnection(); }
- 修复其他代码缺陷:
- 输出流操作的catch块不要吞异常,打印日志后直接抛出,避免后续执行无意义的代码掩盖问题。
- 响应内容拼接改用
StringBuilder,不要在循环里用+=拼接字符串,避免不必要的性能损耗。 - 所有流、连接的关闭逻辑统一放在try-with-resources或finally块中,避免异常场景下连接泄漏。
内容的提问来源于stack exchange,提问作者Md Alif Al Amin
相关产品推荐
相关产品推荐

