Java 1.6+HttpClient4.2.3禁用SSL证书验证解决方案求助
问题描述
架构限制下维护的Maven Web应用用的是旧技术栈:
- Java 1.6
- HttpClient 4.2.3(基于
DefaultHttpClient)
业务流程分两步:
- 发送HTTPS POST请求获取JWT Token
- 携带Token发送HTTPS GET请求跳转至签名表单站点
现在遇到的问题:点击获取Token的跳转链接时,抛出javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated错误。
之前试过一个自定义信任所有证书的方案,但无效,代码如下:
private DefaultHttpClient getDefaultHttpClient(DefaultHttpClient httpClient) throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = null; sslContext = SSLContext.getInstance("SSL"); // set up a TrustManager that trusts everything sslContext.init(null, new TrustManager[] { new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { System.out.println("getAcceptedIssuers ============="); return null; } public void checkClientTrusted(X509Certificate[] certs, String authType) { System.out.println("checkClientTrusted ============="); } public void checkServerTrusted(X509Certificate[] certs, String authType) { System.out.println("checkServerTrusted ============="); } } }, new SecureRandom()); final SSLSocketFactory theSslSocketFactory = new SSLSocketFactory(sslContext); final Scheme theHttpsScheme = new Scheme("https", theSslSocketFactory, 443); httpClient.getConnectionManager().getSchemeRegistry().register(theHttpsScheme); return httpClient; }
原请求代码:
DefaultHttpClient httpClient = new DefaultHttpClient(); try { HttpPost postRequest = new HttpPost(endpointAddress); StringEntity input = new StringEntity(jo.toString(), "UTF-8"); input.setContentType("application/json"); postRequest.setEntity(input); HttpResponse response = httpClient.execute(postRequest); HttpEntity httpEntity = response.getEntity(); String responseString = EntityUtils.toString(httpEntity); JSONObject resultObj = new JSONObject(responseString); String tokenId = resultObj.getString("token"); }catch(Exception e) { e.printStackTrace(); }finally { httpClient.getConnectionManager().shutdown(); }
可行解决方案
方案一:修复自定义信任证书的配置
之前的方案有两个核心问题:一是getAcceptedIssuers返回null,Java 1.6的SSL实现不兼容;二是未禁用主机名验证。修改后的代码如下:
private DefaultHttpClient createTrustAllHttpClient() throws NoSuchAlgorithmException, KeyManagementException { // 自定义信任所有证书的TrustManager X509TrustManager trustAllManager = new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { // 返回空数组而非null,适配Java 1.6的SSL实现 return new X509Certificate[0]; } @Override public void checkClientTrusted(X509Certificate[] certs, String authType) {} @Override public void checkServerTrusted(X509Certificate[] certs, String authType) {} }; // 用TLS初始化SSLContext,比SSL协议更安全 SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, new TrustManager[]{trustAllManager}, new SecureRandom()); // 创建SSLSocketFactory并禁用主机名验证 SSLSocketFactory sslSocketFactory = new SSLSocketFactory(sslContext, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); // 新建HttpClient实例,避免原有连接缓存导致配置不生效 DefaultHttpClient httpClient = new DefaultHttpClient(); SchemeRegistry schemeRegistry = httpClient.getConnectionManager().getSchemeRegistry(); // 替换默认的https协议配置 schemeRegistry.register(new Scheme("https", 443, sslSocketFactory)); return httpClient; }
在请求代码中使用该方法创建HttpClient:
try { DefaultHttpClient httpClient = createTrustAllHttpClient(); HttpPost postRequest = new HttpPost(endpointAddress); StringEntity input = new StringEntity(jo.toString(), "UTF-8"); input.setContentType("application/json"); postRequest.setEntity(input); HttpResponse response = httpClient.execute(postRequest); HttpEntity httpEntity = response.getEntity(); String responseString = EntityUtils.toString(httpEntity); JSONObject resultObj = new JSONObject(responseString); String tokenId = resultObj.getString("token"); }catch(Exception e) { e.printStackTrace(); }
方案二:导入服务器证书到Java信任库(推荐,更安全)
如果不想禁用证书验证,可将目标服务器的SSL证书导入Java 1.6的默认信任库:
- 导出服务器证书:
执行以下命令将证书导出到文件:openssl s_client -connect 目标域名:443 < /dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > server.crt - 导入到信任库:
默认密码为keytool -import -alias target-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file server.crtchangeit,按提示确认导入即可。 - 重启应用,HttpClient会自动信任该证书,无需修改代码。
方案三:指定自定义信任库
如果无法修改系统默认信任库,可在代码中指定自定义信任库:
private DefaultHttpClient createCustomTrustStoreHttpClient(String trustStorePath, String trustStorePassword) throws Exception { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); FileInputStream instream = new FileInputStream(new File(trustStorePath)); try { trustStore.load(instream, trustStorePassword.toCharArray()); } finally { instream.close(); } SSLSocketFactory sslSocketFactory = new SSLSocketFactory(trustStore); DefaultHttpClient httpClient = new DefaultHttpClient(); SchemeRegistry schemeRegistry = httpClient.getConnectionManager().getSchemeRegistry(); schemeRegistry.register(new Scheme("https", 443, sslSocketFactory)); return httpClient; }
使用时传入自定义信任库的路径和密码,信任库需提前导入目标服务器的证书。
内容的提问来源于stack exchange,提问作者Elgün Şükürov
相关产品推荐
相关产品推荐

