這兩天調試鏈接問題有兩個方面:
HttpsURLConnection 中connect 拋出的異常時IOException
採用下列文章描述的
HttpClient httpClient =
new
DefaultHttpClient();
返回SSLException異常
javax.net.ssl.SSLException: hostname in certificate didn't match: <58.83.158.101> != <*.360buy.com>
兩種方法都正確,只是獲取的異常信息後者更爲詳細,以下是調用堆棧
Exception in thread "main" javax.net.ssl.SSLException: hostname in certificate didn't match: <eps.dev.surepush.cn> != <*.xxxxxxx(公司名稱).com>
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:220)
at org.apache.http.conn.ssl.BrowserCompatHostnameVerifier.verify(BrowserCompatHostnameVerifier.java:54)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:149)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:130)
at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:399)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.updateSecureConnection(DefaultClientConnectionOperator.java:203)
at org.apache.http.impl.conn.AbstractPoolEntry.layerProtocol(AbstractPoolEntry.java:277)
at org.apache.http.impl.conn.AbstractPooledConnAdapter.layerProtocol(AbstractPooledConnAdapter.java:138)
at org.apache.http.impl.client.DefaultRequestDirector.establishRoute(DefaultRequestDirector.java:704)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:421)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:641)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:576)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:554)
at com.client.ctl.ZTest.main(ZTest.java:195)
具體SSLSocketFactory實現如下,否則不返回SSLException,最終根據調用堆棧,增加this.getHostnameVerifier().verify(host, sslSocket);
class SSLSocketFactoryEx extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public SSLSocketFactoryEx(KeyStore truststore)
throws NoSuchAlgorithmException, KeyManagementException,
KeyStoreException, UnrecoverableKeyException {
super(truststore);
//
KeyManager[] km = resetKm();
TrustManager[] tm = resetTm();
sslContext.init(km, tm, null);
}
@Override
public Socket createSocket(Socket socket, String host, int port,
boolean autoClose) throws IOException, UnknownHostException {
System.out.println("=== SSLSocketFactoryEx.createSocket === host : " + host);
SSLSocket sslSocket = (SSLSocket)sslContext.getSocketFactory().createSocket(socket, host,
port, autoClose);
// 這裏沒有驗證,增加後就能正常判讀
this.getHostnameVerifier().verify(host, sslSocket);
return sslSocket;
}
@Override
public Socket createSocket() throws IOException {
System.out.println("=== SSLSocketFactoryEx.createSocket ===");
return sslContext.getSocketFactory().createSocket();
}
}
參考:http://my.oschina.net/sourcecoding/blog/80698
今天在開發京東API接口的時候遇到了一個問題。
問題描述:開發京東用戶授權,採用https的訪問協議獲取新的AccessToken時,總是出現異常:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
之後在網上找了很多例子,最後摘了一個不需要證書的例子:
之後在運行報另一個異常,執行HttpResponse response = httpClient.execute(httpPost); 報的錯:
javax.net.ssl.SSLException: hostname in certificate didn't match: <58.83.158.101> != <*.360buy.com>
原因我猜測可能是在驗證HOST的時候出現了問題,但我始終傳給程序的是域名,爲啥就非要解析成ip呢。。。我鬱悶。我就一直在想怎麼能報HOST變成域名的形式,我debug了httpPost裏的HOST沒有問題還是域名的,我就想那肯定是httpClient中有問題。最後發現httpClient與socketFactory有關係,而socketFactory就像是一個配置信息的存儲,我在這裏找到了一個setHostnameVerifier的方法,貌似是驗證hostName的,需要參數hostnameVerifier,我像之前那個X509TrustManager一樣new了一個X509HostnameVerifier付給setHostnameVerifier方法,然後重寫裏面的方法,發現了驗證的方法verify(String arg0, SSLSession arg1),跳過直接返回true,果然問題解決了。
以下爲正確代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
/** * 發送HTTPS POST請求 *
* @param 要訪問的HTTPS地址,POST訪問的參數Map對象 * <a href="http://my.oschina.net/u/556800" class="referer" target="_blank">@return</a> 返回響應值 * */ public
static final
String sendHttpsRequestByPost(String url, Map<String, String> params) { String responseContent =
null ; HttpClient httpClient =
new DefaultHttpClient(); //創建TrustManager X509TrustManager xtm =
new X509TrustManager() { public
void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {} public
void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {} public
X509Certificate[] getAcceptedIssuers() { return
null ; } }; //這個好像是HOST驗證 X509HostnameVerifier hostnameVerifier =
new X509HostnameVerifier() { public
boolean verify(String arg0, SSLSession arg1) { return
true ; } public
void verify(String arg0, SSLSocket arg1)
throws IOException {} public
void verify(String arg0, String[] arg1, String[] arg2)
throws SSLException {} public
void verify(String arg0, X509Certificate arg1)
throws SSLException {} }; try
{ //TLS1.0與SSL3.0基本上沒有太大的差別,可粗略理解爲TLS是SSL的繼承者,但它們使用的是相同的SSLContext SSLContext ctx = SSLContext.getInstance( "TLS" ); //使用TrustManager來初始化該上下文,TrustManager只是被SSL的Socket所使用 ctx.init( null ,
new TrustManager[] { xtm },
null ); //創建SSLSocketFactory SSLSocketFactory socketFactory =
new SSLSocketFactory(ctx); socketFactory.setHostnameVerifier(hostnameVerifier); //通過SchemeRegistry將SSLSocketFactory註冊到我們的HttpClient上 httpClient.getConnectionManager().getSchemeRegistry().register( new
Scheme( "https" , socketFactory,
443 )); HttpPost httpPost =
new HttpPost(url); List<NameValuePair> formParams =
new ArrayList<NameValuePair>();
// 構建POST請求的表單參數 for
(Map.Entry<String, String> entry : params.entrySet()) { formParams.add( new
BasicNameValuePair(entry.getKey(), entry.getValue())); } httpPost.setEntity( new
UrlEncodedFormEntity(formParams, "UTF-8" )); HttpResponse response = httpClient.execute(httpPost); HttpEntity entity = response.getEntity();
// 獲取響應實體 if
(entity != null ) { responseContent = EntityUtils.toString(entity,
"UTF-8" ); } }
catch (KeyManagementException e) { e.printStackTrace(); }
catch (NoSuchAlgorithmException e) { e.printStackTrace(); }
catch (UnsupportedEncodingException e) { e.printStackTrace(); }
catch (ClientProtocolException e) { e.printStackTrace(); }
catch (ParseException e) { e.printStackTrace(); }
catch (IOException e) { e.printStackTrace(); }
finally { // 關閉連接,釋放資源 httpClient.getConnectionManager().shutdown(); } return
responseContent; } |
爲了大家不引錯包,我把import也放在這裏:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
import
java.io.IOException; import
java.io.UnsupportedEncodingException; import
java.lang.reflect.Field; 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; import
javax.net.ssl.SSLContext; import
javax.net.ssl.SSLException; import
javax.net.ssl.SSLSession; import
javax.net.ssl.SSLSocket; import
javax.net.ssl.TrustManager; import
javax.net.ssl.X509TrustManager; import
org.apache.http.HttpEntity; import
org.apache.http.HttpResponse; import
org.apache.http.NameValuePair; import
org.apache.http.ParseException; import
org.apache.http.client.ClientProtocolException; 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.conn.ssl.X509HostnameVerifier; import
org.apache.http.impl.client.DefaultHttpClient; import
org.apache.http.message.BasicNameValuePair; import
org.apache.http.util.EntityUtils; |