開發/測試環境常遇到伺服器使用**自簽名**、**過期**、**域名不匹配**的 SSL 憑證,導致 javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated。本文提供基於 **Apache HttpComponents Client 4.x** 的完整解決方案:自定義 TrustManager 信任所有憑證,註冊至 HttpClient 實現 HTTPS POST 請求。
X509TrustManager(三個方法體為空)。SSLContext(協議 TLS)。SSLSocketFactory 並註冊至 SchemeRegistry(協議 https、埠 443)。HttpPost、設定參數、執行請求。
package com.example.util;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class HttpsPostUtil {
/**
* 向 HTTPS URL 發送 POST 表單請求,信任所有伺服器憑證
*
* @param url 請求地址
* @param params 表單參數 Map
* @return 響應字串,失敗返回 null
*/
public static String sendHttpsPost(String url, Map params) {
HttpClient httpClient = new DefaultHttpClient();
String responseContent = null;
try {
// 1. 建立信任所有憑證的 TrustManager
TrustManager trustAll = new X509TrustManager() {
@Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
@Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
@Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
};
// 2. 初始化 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustAll}, null);
// 3. 建立 SSLSocketFactory 並註冊
SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext);
httpClient.getConnectionManager().getSchemeRegistry()
.register(new Scheme("https", 443, socketFactory));
// 4. 構建 POST 請求
HttpPost httpPost = new HttpPost(url);
List formParams = new ArrayList<>();
for (Map.Entry entry : params.entrySet()) {
formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
httpPost.setEntity(new UrlEncodedFormEntity(formParams, "UTF-8"));
// 5. 執行並處理響應
HttpResponse response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
if (entity != null) {
responseContent = EntityUtils.toString(entity, "UTF-8");
EntityUtils.consume(entity); // 釋放連接
}
// 除錯資訊
System.out.println("請求地址: " + httpPost.getURI());
System.out.println("響應狀態: " + response.getStatusLine());
System.out.println("響應內容: " + responseContent);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
httpClient.getConnectionManager().shutdown(); // 關鍵:釋放連接管理器資源
}
return responseContent;
}
// 測試主方法
public static void main(String[] args) throws Exception {
Map params = new HashMap<>();
params.put("TransName", "IQSR");
params.put("Plain", "transId=IQSR~|~originalorderId=2012~|~originalTransAmt=~|~merURL=");
params.put("Signature", "9b759887e6ca9d4c24509d22ee4d22494d0dd2dfbdbeaab3545c1acee62eec7");
String result = sendHttpsPost("https://www.cebbank.com/per/QueryMerchantEpay.do", params);
System.out.println("最終結果: " + result);
}
}
| 問題 | 解決 |
|---|---|
HttpClient 4.3+ 棄用 SchemeRegistry | 改用 HttpClientBuilder + SSLConnectionSocketFactory + RegistryBuilder(見下方現代寫法) |
| 多執行緒共用 HttpClient | DefaultHttpClient 線程安全,但建議用 PoolingHttpClientConnectionManager 管理連接池 |
| 憑信所有憑證的安全風險 | **僅限開發/測試環境**!生產環境必須載入正規 CA 憑證或 Pinning |
| 中文參數亂碼 | UrlEncodedFormEntity(formParams, "UTF-8") 明確指定編碼 |
| 連接洩漏 | finally 中 shutdown() 或 close() 必須執行 |
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
// ...
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustAll}, null);
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
sslContext, NoopHostnameVerifier.INSTANCE);
Registry registry = RegistryBuilder.create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslSocketFactory)
.build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
cm.setMaxTotal(200);
cm.setDefaultMaxPerRoute(50);
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(cm)
.setDefaultRequestConfig(RequestConfig.custom()
.setConnectTimeout(5000)
.setSocketTimeout(10000)
.build())
.build();
// 後續 httpPost 執行邏輯相同
自定義 TrustManager 繞過憑證驗證是解決開發環境 HTTPS 請求失效的標準手段。掌握 SSLContext → SocketFactory → SchemeRegistry/ConnectionManager 的註冊鏈路,並注意**資源釋放**、**線程安全**、**生產環境禁用**,即可從容應對各類 HTTPS 整合場景。