Https請求
一掉房、訪問HTTPS站點
兩種方法來模擬發(fā)送HTTP請求茧跋,訪問HTTP站點。一種方式是通過java.net自帶的HttpURLConnection卓囚,另一種方式是通過Apache的HttpClient瘾杭,這兩種方式各有各的優(yōu)勢。這里也使用這兩種方式來訪問HTTPS站點哪亿,從下面的代碼可以看到粥烁,和前面訪問HTTP站點幾乎完全一樣。
1.1使用HttpURLConnection
@Test
public void basicHttpsGet() throws
Exception {
String url =? "https://www.baidu.com";
URL obj = new
URL(url);
HttpsURLConnection
con = (HttpsURLConnection) obj.openConnection();
con.setRequestProperty("User-Agent",
"Mozilla/5.0 (Windows NT 6.1; WOW64) ...");
con.setRequestProperty("Accept-Language",
"en-US,en;q=0.5");
con.setRequestMethod("GET");
String
responseBody = readResponseBody(con.getInputStream());
System.out.println(responseBody);
}
1.2使用HttpClient
@Test
public void basicHttpsGet() throws
Exception {
String url =? "https://www.baidu.com";
HttpGet request =
new HttpGet(url);
request.setHeader("User-Agent",
"Mozilla/5.0 (Windows NT 6.1; WOW64) ...");
CloseableHttpClient
httpclient = HttpClients.createDefault();
CloseableHttpResponse
response = httpclient.execute(request);
String
responseBody = readResponseBody(response);
System.out.println(responseBody);
}
具體的代碼解釋參見第一篇博客蝇棉,這里不再贅述讨阻。一般情況下,訪問HTTPS站點就和訪問HTTP站點一樣簡單篡殷,無論是HttpURLConnection還是HttpClient变勇,都將底層的實現(xiàn)細節(jié)封裝了起來,給我們提供了一致的對外接口贴唇,所以我們不用關(guān)心HTTPS的實現(xiàn)原理搀绣。
二、Java里的證書
上面所介紹的是瀏覽器對證書進行驗證的過程戳气,瀏覽器保存了一個常用的CA證書列表链患,在驗證證書鏈的有效性時,直接使用保存的證書里的公鑰進行校驗瓶您,如果在證書列表中沒有找到或者找到了但是校驗不通過麻捻,那么瀏覽器會警告用戶纲仍,由用戶決定是否繼續(xù)。與此類似的贸毕,操作系統(tǒng)也一樣保存有一份可信的證書列表郑叠,譬如在Windows系統(tǒng)下,你可以運行certmgr.msc打開證書管理器查看明棍,這些證書實際上是存儲在Windows的注冊表中乡革,一般情況下位于:\SOFTWARE\Microsoft\SystemCertificates\路徑下。那么在Java程序中是如何驗證證書的呢摊腋?
和瀏覽器操作系統(tǒng)類似沸版,Java在JRE的安裝目錄下也保存了一份默認可信的證書列表,這個列表一般是保存在$JRE/lib/security/cacerts文件中兴蒸。要查看這個文件视粮,可以使用類似KeyStore Explorer這樣的軟件,當然也可以使用JRE自帶的keytool工具(后面再介紹)橙凳,cacerts文件的默認密碼為changeit(但是我保證蕾殴,大多數(shù)人都不會change it)。
我們知道岛啸,證書有很多種不同的存儲格式区宇,譬如CA在發(fā)布證書時,常常使用PEM格式值戳,這種格式的好處是純文本议谷,內(nèi)容是BASE64編碼的,證書中使用"-----BEGIN CERTIFICATE-----"和"-----END CERTIFICATE-----"來標識堕虹。另外還有比較常用的二進制DER格式卧晓,在Windows平臺上較常使用的PKCS#12格式等等。當然赴捞,不同格式的證書之間是可以相互轉(zhuǎn)換的逼裆,我們可以使用openssl這個命令行工具來轉(zhuǎn)換,參考SSL
Converter赦政,另外胜宇,想了解更多證書格式的,可以參考這里:Various
SSL/TLS Certificate File Types/Extensions恢着。
在Java平臺下桐愉,證書常常被存儲在KeyStore文件中,上面說的cacerts文件就是一個KeyStore文件掰派,KeyStore不僅可以存儲數(shù)字證書从诲,還可以存儲密鑰,存儲在KeyStore文件中的對象有三種類型:Certificate靡羡、PrivateKey和SecretKey系洛。Certificate就是證書俊性,PrivateKey是非對稱加密中的私鑰,SecretKey用于對稱加密描扯,是對稱加密中的密鑰定页。KeyStore文件根據(jù)用途,也有很多種不同的格式:JKS绽诚、JCEKS典徊、PKCS12、DKS等等憔购,PixelsTech上有一系列文章對KeyStore有深入的介紹宫峦,可以學習下:Different
types of keystore in Java岔帽。
到目前為止玫鸟,我們所說的KeyStore其實只是一種文件格式而已,實際上在Java的世界里KeyStore文件分成兩種:KeyStore和TrustStore犀勒,這是兩個比較容易混淆的概念屎飘,不過這兩個東西從文件格式來看其實是一樣的。KeyStore保存私鑰贾费,用來加解密或者為別人做簽名钦购;TrustStore保存一些可信任的證書,訪問HTTPS時對被訪問者進行認證褂萧,以確保它是可信任的押桃。所以準確來說,上面的cacerts文件應(yīng)該叫做TrustStore而不是KeyStore导犹,只是它的文件格式是KeyStore文件格式罷了唱凯。
除了KeyStore和TrustStore,Java里還有兩個類KeyManager和TrustManager與此息息相關(guān)谎痢。JSSE的參考手冊中有一張示意圖磕昼,說明了各個類之間的關(guān)系:
可以看出如果要進行SSL會話,必須得新建一個SSLSocket對象节猿,而SSLSocket對象是通過SSLSocketFactory來管理的票从,SSLSocketFactory對象則依賴于SSLContext,SSLContext對象又依賴于keyManager滨嘱、TrustManager和SecureRandom峰鄙。我們這里最關(guān)心的是TrustManager對象,另外兩個暫且忽略太雨,因為正是TrustManager負責證書的校驗先馆,對網(wǎng)站進行認證,要想在訪問HTTPS時通過認證躺彬,不報sun.security.validator.ValidatorException異常煤墙,必須從這里開刀梅惯。
三、Java客戶端訪問https時證書驗證處理規(guī)則
客戶端的TrustStore文件中保存著被客戶端所信任的服務(wù)器的證書信息仿野∠臣酰客戶端在進行SSL連接時,JSSE將根據(jù)這個文件中的證書決定是否信任服務(wù)器端的證書脚作。在SunJSSE中葫哗,有一個信任管理器類負責決定是否信任遠端的證書,這個類有如下的處理規(guī)則:
1)
若系統(tǒng)屬性javax.net.sll.trustStore指定了TrustStore文件球涛,那么信任管理器就去jre安裝路徑下的lib/security/目錄中尋找并使用這個文件來檢查證書劣针。
2)
若該系統(tǒng)屬性沒有指定TrustStore文件,它就會去jre安裝路徑下尋找默認的TrustStore文件亿扁,這個文件的相對路徑為:lib/security/jssecacerts捺典。
3)
若jssecacerts不存在,但是cacerts存在(它隨J2SDK一起發(fā)行从祝,含有數(shù)量有限的可信任的基本證書)襟己,那么這個默認的TrustStore文件就是lib/security/cacerts。
四牍陌、自定義TrustManager繞過證書檢查進行https訪問
我們知道了TrustManager是專門負責校驗證書的擎浴,那么最容易想到的方法應(yīng)該就是改寫TrustManager類,讓它不要對證書做校驗毒涧,這種方法雖然粗暴贮预,但是卻相當有效,而且Java中的TrustManager也確實可以被重寫契讲,下面是示例代碼:
@Test
public void
basicHttpsGetIgnoreCertificateValidation() throws Exception {
String url =? "https://kyfw.12306.cn/otn/";
// Create a trust
manager that does not validate certificate chains
TrustManager[]
trustAllCerts = new TrustManager[] {
new
X509TrustManager() {
public
X509Certificate[] getAcceptedIssuers() {
return
null;
}
public
void checkClientTrusted(X509Certificate[] certs, String authType) {
//
don't check
}
public
void checkServerTrusted(X509Certificate[] certs, String authType) {
//
don't check
}
}
};
SSLContext ctx =
SSLContext.getInstance("TLS");
ctx.init(null,
trustAllCerts, null);
LayeredConnectionSocketFactory
sslSocketFactory = new SSLConnectionSocketFactory(ctx);
CloseableHttpClient
httpclient = HttpClients.custom()
.setSSLSocketFactory(sslSocketFactory)
.build();
HttpGet request =
new HttpGet(url);
request.setHeader("User-Agent",
"Mozilla/5.0 (Windows NT 6.1; WOW64) ...");
CloseableHttpResponse
response = httpclient.execute(request);
String responseBody
= readResponseBody(response);
System.out.println(responseBody);
}
我們新建了一個匿名類仿吞,繼承自X509TrustManager接口,這個接口提供了三個方法用于驗證證書的有效性:getAcceptedIssuers怀泊、checkClientTrusted茫藏、checkServerTrusted,我們在驗證的函數(shù)中直接返回霹琼,不做任何校驗务傲,這樣在訪問HTTPS站點時,就算是證書不可信枣申,也不會拋出異常售葡,可以繼續(xù)執(zhí)行下去。
這種方法雖然簡單忠藤,但是卻有一個最嚴重的問題挟伙,就是不安全。因為不對證書做任何合法性校驗模孩,而且這種處理是全局性的尖阔,不管青紅皂白贮缅,所有的證書都不會做驗證,所以就算遇到不信任的證書介却,代碼依然會繼續(xù)與之通信谴供,至于通信的數(shù)據(jù)安全不安全就不能保證了。所以如果你只是想在測試環(huán)境做個實驗齿坷,那沒問題桂肌,但是如果你要將代碼發(fā)布到生產(chǎn)環(huán)境,請慎重永淌。
五崎场、使用證書進行https訪問
對于有些證書,我們基本上確定是可以信任的遂蛀,但是這些證書又不在Java的cacerts文件中谭跨,譬如12306網(wǎng)站,或者使用了Let's Encrypt證書的一些網(wǎng)站答恶,對于這些網(wǎng)站饺蚊,我們可以將其添加到信任列表中萍诱,而不是使用上面的方法統(tǒng)統(tǒng)都相信悬嗓,這樣程序的安全性仍然可以得到保障。
5.1使用keytool導(dǎo)入證書
簡單的做法是將這些網(wǎng)站的證書導(dǎo)入到cacerts文件中裕坊,這樣Java程序在校驗證書的時候就可以從cacerts文件中找到并成功校驗這個證書了包竹。上面我們介紹過JRE自帶的keytool這個工具,這個工具小巧而強悍籍凝,擁有很多功能周瞎。首先我們可以使用它查看KeyStore文件,使用下面的命令可以列出KeyStore文件中的所有內(nèi)容(包括證書饵蒂、私鑰等):
$ keytool -list -keystore cacerts
然后通過下面的命令声诸,將證書導(dǎo)入到cacerts文件中:
$ keytool -import -alias 12306 -keystore cacerts
-file 12306.cer
要想將網(wǎng)站的證書導(dǎo)入cacerts文件中,首先要獲取網(wǎng)站的證書退盯,譬如上面命令中的12306.cer文件彼乌,它是使用瀏覽器的證書導(dǎo)出向?qū)П4娴摹H缦聢D所示:
關(guān)于keytool的更多用法渊迁,可以參考keytool的官網(wǎng)手冊慰照,SSLShopper上也有一篇文章列出了常用的keytool命令。
5.2使用KeyStore動態(tài)加載證書
使用keytool導(dǎo)入證書琉朽,這種方法不僅簡單毒租,而且保證了代碼的安全性,最關(guān)鍵的是代碼不用做任何修改箱叁。所以我比較推薦這種方法墅垮。但是這種方法有一個致命的缺陷惕医,那就是你需要修改JRE目錄下的文件,如果你的程序只是在自己的電腦上運行算色,那倒沒什么曹锨,可如果你的程序要部署在其他人的電腦上或者公司的服務(wù)器上,而你沒有權(quán)限修改JRE目錄下的文件剃允,這該怎么辦沛简?如果你的程序是一個分布式的程序要部署在成百上千臺機器上,難道還得修改每臺機器的JRE文件嗎斥废?好在我們還有另一種通過編程的手段來實現(xiàn)的思路椒楣,在代碼中動態(tài)的加載KeyStore文件來完成證書的校驗,抱著知其然知其所以然的態(tài)度牡肉,我們在最后也實踐下這種方法捧灰。通過編寫代碼可以更深刻的了解KeyStore、TrustManagerFactory统锤、SSLContext以及SSLSocketFactory這幾個類之間的關(guān)系毛俏。
@Test
public void
basicHttpsGetUsingSslSocketFactory() throws Exception {
String
keyStoreFile = "D:\\code\\ttt.ks";
String password =
"poiuyt";
KeyStore ks =
KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream
in = new FileInputStream(keyStoreFile);
ks.load(in,
password.toCharArray());
System.out.println(KeyStore.getDefaultType().toString());
System.out.println(TrustManagerFactory.getDefaultAlgorithm().toString());
TrustManagerFactory
tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
SSLContext ctx =
SSLContext.getInstance("TLS");
ctx.init(null,
tmf.getTrustManagers(), null);
LayeredConnectionSocketFactory
sslSocketFactory = new SSLConnectionSocketFactory(ctx);
String url =? "https://ttt.aneasystone.com";
/**
* Return
the page with content:
*? 401
Authorization Required
*/
CloseableHttpClient
httpclient = HttpClients.custom()
.setSSLSocketFactory(sslSocketFactory)
.build();
HttpGet request =
new HttpGet(url);
request.setHeader("User-Agent",
"Mozilla/5.0 (Windows NT 6.1; WOW64) ...");
CloseableHttpResponse
response = httpclient.execute(request);
String
responseBody = readResponseBody(response);
System.out.println(responseBody);
}
上面的代碼使用了HttpClient,如果是使用HttpsURLConnection只需要改動下面兩行即可:
HttpsURLConnection con =
(HttpsURLConnection) obj.openConnection();
con.setSSLSocketFactory(ctx.getSocketFactory());
最后的最后饲窿,我們還可以通過下面的屬性來指定trustStore煌寇,這樣也不需要編寫像上面那樣大量繁瑣的代碼,另外逾雄,參考我前面的博客阀溶,這些屬性還可以通過JVM的參數(shù)來設(shè)置。
System.setProperty("javax.net.ssl.trustStore",
"D:\\code\\ttt.ks");
System.setProperty("javax.net.ssl.trustStorePassword",
"poiuyt");
若沒有通過設(shè)置JVM參數(shù)來指定要加載的證書庫文件鸦泳,則使用jdk默認的jre\\lib\\cacerts證書庫文件來驗證請求站點的證書是否合法银锻。
5.3 Java請求https服務(wù)(真?zhèn)尾樵儗嵗?/p>
publicstaticJSONObject SetSystsHttpsSSLPost(Stringurl, Stringargs, Stringappkey, Stringiv)throwsException {
JSONObjectresult=newJSONObject();
Stringargs= SSLPostTest.encryption(querydata);
Stringiv=newBase64().encodeToString(IV.getBytes());
StringkeyStoreFile="C:\\Users\\Gufung\\nubiacsm.keystore";
/***方式一:使用keystore加載證書庫文件****/
/*String keyStoreFile = System.getProperty("java.home")+ "\\lib\\security\\cacerts";
Stringpassword = "changeit";
KeyStoreks= KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStreamin = new FileInputStream(keyStoreFile);
ks.load(in,password.toCharArray());
TrustManagerFactorytmf= TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
SSLContextctx= SSLContext.getInstance("TLS");
ctx.init(null,tmf.getTrustManagers(), null);
URL myURL = new URL(url);
HttpsURLConnectioncon= (HttpsURLConnection) myURL.openConnection();
con.setSSLSocketFactory(ctx.getSocketFactory());*/
/***方式一:使用keystore加載證書庫文件****/
/***方式二:通過設(shè)置JVM參數(shù)來指定要加載的證書庫文件****/
System.setProperty("javax.net.ssl.trustStore",keyStoreFile);
System.setProperty("javax.net.ssl.trustStorePassword","changeit");
URLmyURL=newURL(url);
HttpsURLConnectioncon= (HttpsURLConnection)myURL.openConnection();
/***方式二:通過設(shè)置JVM參數(shù)來指定要加載的證書庫文件****/
con.setDoOutput(true);
con.setDoInput(true);
con.setRequestMethod("POST");
con.setUseCaches(false);
con.connect();
DataOutputStreamout=newDataOutputStream(con.getOutputStream());
Stringcontent="args="+ URLEncoder.encode(args,"UTF-8");
content+="&appkey="+ URLEncoder.encode(appkey,"UTF-8");
content+="&iv="+ URLEncoder.encode(iv,"UTF-8");
out.writeBytes(content);
out.flush();
out.close();
intresultCode=con.getResponseCode();
System.out.println(resultCode);
if(HttpURLConnection.HTTP_OK==resultCode) {
StringreadLine=newString();
BufferedReaderresponseReader=newBufferedReader(
newInputStreamReader(con.getInputStream(),"UTF-8"));
while((readLine=responseReader.readLine()) !=null) {
result=newJSONObject(readLine);
}
responseReader.close();
}
con.disconnect();
System.out.println(result.toString());
returnresult;
}
參考
Different types of keystore in Java -- Overview
Different types of keystore in Java -- JKS
Java中用HttpsURLConnection訪問Https鏈接的問題
Where is the certificate folder in Windows 7?
Difference between trustStore and keyStore in Java - SSL
Java Secure Socket Extension (JSSE) Reference Guide
Disable Certificate Validation in Java SSL Connections
javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException: PKIX path building failed
How to solve javax.net.ssl.SSLHandshakeException?
The Most Common Java Keytool Keystore Commands
keytool - Key and Certificate Management Tool
原文鏈接:http://www.aneasystone.com/archives/2016/04/java-and-https.html
參考資料:Java安全通信:HTTPS與SSL
http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html
http://www.aneasystone.com/archives/2016/04/java-and-https.html
http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html
https://my.oschina.net/zhlmmc/blog/42111
https://www.sslshopper.com/article-most-common-java-keytool-keystore-commands.html
http://blog.csdn.net/csdnbenbenchong/article/details/7388260
http://blog.csdn.net/u011042133/article/details/51671801
http://snowolf.iteye.com/blog/397693
http://www.iamlbk.com/blog/20160731/tomcat-https/?utm_source=tuicool&utm_medium=referral
http://blog.chenxiaosheng.com/posts/2013-12-26/java-use-self_signed_certificate.html
http://www.zhixing123.cn/jsp/49937.html
http://www.cnblogs.com/JeffreySun/archive/2010/06/24/1627247.html
https://publib.boulder.ibm.com/tividd/td/TRM/SC23-4822-00/zh_CN/HTML/user276.htm
http://op.baidu.com/2015/04/https-s01a01/
http://lukejin.iteye.com/blog/605634
http://ln-ydc.iteye.com/blog/1335213
https://blog.cnbluebox.com/blog/2014/03/24/shu-zi-zheng-shu/
http://blog.csdn.net/sfdev/article/details/2957240
https://segmentfault.com/a/1190000002554673#articleHeader0
http://www.cnblogs.com/devinzhang/archive/2012/02/28/2371631.html