一、前言
本篇風(fēng)格會(huì)偏向講故事,來(lái)記錄整個(gè)發(fā)現(xiàn)問(wèn)題,解決問(wèn)題的過(guò)程。具體的知識(shí)點(diǎn)總結(jié)放在后一篇。
前段陣子被分配了一個(gè)工單,要求抓取另一個(gè)險(xiǎn)企B的數(shù)據(jù)。想著應(yīng)該不會(huì)比上一家A麻煩了,險(xiǎn)企A抓取數(shù)據(jù)過(guò)程中有幾次請(qǐng)求是跨域的,很多數(shù)據(jù)都是由ajax動(dòng)態(tài)請(qǐng)求到的,要分析js代碼,模擬請(qǐng)求。
稍微觀察了一下險(xiǎn)企B的頁(yè)面源代碼,發(fā)現(xiàn)所有操作除了表單提交,其他都是get請(qǐng)求。而且模擬登錄時(shí)不需要輸驗(yàn)證碼。美滋滋。。就是有2點(diǎn)麻煩的地方:
- 險(xiǎn)企B是通過(guò)專線訪問(wèn)的,只有借助代理公司的網(wǎng)絡(luò)才能訪問(wèn),公司在代理公司那放了臺(tái)電腦,然后我在公司遠(yuǎn)程連接那臺(tái)電腦進(jìn)行開(kāi)發(fā)的。操作會(huì)有延時(shí),有點(diǎn)卡。
- 險(xiǎn)企B的網(wǎng)站看起來(lái)很古老,只支持ie8及以下的瀏覽器訪問(wèn),chrome、火狐啥的就更打不開(kāi)了。所以抓包都靠fiddler了,頁(yè)面解析元素定位就只能靠舊版本的ie開(kāi)發(fā)工具,
好吧,雖然不便,但是還是不怎么影響開(kāi)發(fā)過(guò)程。
然后在一開(kāi)始,訪問(wèn)第一個(gè)登錄頁(yè)面的時(shí)候我就被卡住了。我用原來(lái)的工具類發(fā)了一個(gè)get請(qǐng)求去獲取登錄頁(yè)面,結(jié)果報(bào)錯(cuò)了。
二、錯(cuò)誤1
debug:
Unsupported record version SSLv2Hello
javax.net.ssl.SSLException: Unsupported record version SSLv2Hello
at sun.security.ssl.InputRecord.readV3Record(Unknown Source)
at sun.security.ssl.InputRecord.read(Unknown Source)
at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:275)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:254)
at org.apache.http.impl.conn.HttpClientConnectionOperator.connect(HttpClientConnectionOperator.java:123)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:318)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:363)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:219)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:195)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:86)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:108)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
at httpcomponents.httpsTest.main(httpsTest.java:135)
一臉懵逼。直覺(jué)上是遇到了什么麻煩的東西。直接去Stack Overflow上面搜索了。發(fā)現(xiàn)有個(gè)相同錯(cuò)誤的問(wèn)題。
里面的答案大致就是說(shuō),我所要請(qǐng)求的這個(gè)server很古老,居然還支持SSLv2協(xié)議(還用了個(gè)incredibly加強(qiáng)語(yǔ)氣,-_-||)。
2.1 解決方案:
使用SSLConnectionSocketFactory來(lái)強(qiáng)制只允許使用TLSv1協(xié)議。我的代碼如下:
// ssl context
SSLContext sslcontext = SSLContexts.custom().build();
// ssl socket factory
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[]{"TLSv1"},
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
// httClient 實(shí)例
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
//.setDefaultCookieStore(cookieStore)
// 異常重試機(jī)制 3次 (網(wǎng)絡(luò)層面上的)
//.setRetryHandler(new DefaultHttpRequestRetryHandler(3,true))
//.setDefaultRequestConfig(defaultRequestConfig)
.build();
至于這些安全協(xié)議,在下一章會(huì)總結(jié)。
上述代碼加進(jìn)去之后呢。。之前那個(gè)錯(cuò)誤是解決了。然后又出現(xiàn)了新的錯(cuò)誤。
三、錯(cuò)誤2
Exception in thread "main" javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:946)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1339)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1323)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1091)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:250)
at com.labcorp.efone.vendor.TestATTConnectivity.main(TestATTConnectivity.java:43)
Caused by: java.io.EOFException: SSL peer shut down incorrectly
at sun.security.ssl.InputRecord.read(InputRecord.java:482)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:927)
... 8 more
看樣子好像是握手時(shí)候失敗了。。又滾去Stack Overflow上去搜索了一下,發(fā)現(xiàn)這還是個(gè)很火的問(wèn)題。大家出問(wèn)題的原因都不一樣,也沒(méi)有個(gè)綜合的答案。所以我就不放出來(lái)了,如果有興趣可以自己去看看。
我的這個(gè)問(wèn)題涉及到了 SSL/TLS 的握手和通信過(guò)程中,安全認(rèn)證被分為單向認(rèn)證和雙向認(rèn)證。這里面的知識(shí)點(diǎn)也很多,具體下一篇總結(jié)篇。雙向認(rèn)證就是說(shuō),server也會(huì)要求驗(yàn)證client的證書,而我用Java程序模擬時(shí)沒(méi)有啟用證書,所以導(dǎo)致認(rèn)證階段出錯(cuò),握手失敗。
3.1 解決方法
相關(guān)圖片由于那時(shí)候忘了截圖,我直接引用的是參考資料中的。
1、訪問(wèn)https網(wǎng)站,下載證書
a. 瀏覽器地址欄旁邊會(huì)有一個(gè)鎖的圖標(biāo),點(diǎn)擊那個(gè)鎖,會(huì)有查看證書的按鈕;
b. 將證書信息導(dǎo)出,證書格式有很多種,der、cer等,我保存的是cer格式的
2、利用jdk的toolkey工具,將證書轉(zhuǎn)換成密鑰的形式
命令行或者shell執(zhí)行下列命令:
keytool -import -alias "my alipay cert" -file steven.cert -keystore my.store,

之后還需要設(shè)置密碼,我直接設(shè)置成123456。
3、sslContext中載入信用證書
private static SSLContext sslcontext;
try {
sslcontext = SSLContexts.custom()
.loadTrustMaterial(new File("D:\\my.keystore"), "123456".toCharArray(),
new TrustSelfSignedStrategy())
.build();
} catch (Exception e) {
e.printStackTrace();
}
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[]{"TLSv1"},
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
httpClient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.setDefaultCookieStore(cookieStore)
// 異常重試機(jī)制 3次 (網(wǎng)絡(luò)層面上的)
.setRetryHandler(new DefaultHttpRequestRetryHandler(3,true))
.setDefaultRequestConfig(defaultRequestConfig)
.build();
然后就解決了。
3.2 SSLHandshake 階段的另一種報(bào)錯(cuò)
Btw,javax.net.ssl.SSLHandshakeException還有一種常見(jiàn)的錯(cuò)誤:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed
這個(gè)就是服務(wù)端的證書是不可信的情況。你可以理解為當(dāng)你用瀏覽器訪問(wèn)某個(gè)網(wǎng)站時(shí),頁(yè)面彈出該網(wǎng)站證書不可信的情況。在這里就是當(dāng)然這種錯(cuò)誤我是沒(méi)有遇到。解決方法詳見(jiàn)http://zhuyuehua.iteye.com/blog/1102347
訪問(wèn)https服務(wù)其他的常見(jiàn)錯(cuò)誤
- java.net.ConnectException: Connection refused: connect 服務(wù)器沒(méi)有啟動(dòng)
- java.net.SocketException: Software caused connection abort: recv failed
這是由于服務(wù)端配置的是SSL雙向認(rèn)證,而客戶端發(fā)送數(shù)據(jù)是按照服務(wù)器是單向認(rèn)證時(shí)發(fā)送的,即沒(méi)有將客戶端證書信息一起發(fā)送給服務(wù)端。 - org.apache.commons.httpclient.NoHttpResponseException 這一般是服務(wù)端防火墻的原因。攔截了客戶端請(qǐng)求。另外,當(dāng)服務(wù)端負(fù)載過(guò)重時(shí),也會(huì)出現(xiàn)此問(wèn)題。將客戶端證書信息一起發(fā)送給服務(wù)端。
參考資料
[1] http://zhuyuehua.iteye.com/blog/1122670
[2] https://blog.csdn.net/wanglha/article/details/51140846
[4] https://blog.csdn.net/liuxiao723846/article/details/52695549