Java通過SSLEngine與NIO實(shí)現(xiàn)HTTPS訪問的操作方法
Java使用NIO進(jìn)行HTTPS協(xié)議訪問的時(shí)候,離不開SSLContext和SSLEngine兩個(gè)類。我們只需要在Connect操作、Connected操作、Read和Write操作中加入SSL相關(guān)的處理即可。
一、連接服務(wù)器之前先初始化SSLContext并設(shè)置證書相關(guān)的操作。
public void Connect(String host, int port) { mSSLContext = this.InitSSLContext(); super.Connect(host, port); }
在連接服務(wù)器前先創(chuàng)建SSLContext對(duì)象,并進(jìn)行證書相關(guān)的設(shè)置。如果服務(wù)器不是使用外部公認(rèn)的認(rèn)證機(jī)構(gòu)生成的密鑰,可以使用基于公鑰CA的方式進(jìn)行設(shè)置證書。如果是公認(rèn)的認(rèn)證證書一般只需要加載Java KeyStore即可。
1.1 基于公鑰CA
public SSLContext InitSSLContext() throws NoSuchAlgorithmException{ // 創(chuàng)建生成x509證書的對(duì)象 CertificateFactory caf = CertificateFactory.getInstance("X.509"); // 這里的CA_PATH是服務(wù)器的ca證書,可以通過瀏覽器保存Cer證書(Base64和DER都可以) X509Certificate ca = (X509Certificate)caf.generateCertificate(new FileInputStream(CA_PATH)); KeyStore caKs = KeyStore.getInstance("JKS"); caKs.load(null, null); // 將上面創(chuàng)建好的證書設(shè)置到倉庫里面,前面的`baidu-ca`只是一個(gè)別名可以任意不要出現(xiàn)重復(fù)即可。 caKs.setCertificateEntry("baidu-ca", ca); TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(caKs); // 最后創(chuàng)建SSLContext,將可信任證書列表傳入。 SSLContext context = SSLContext.getInstance("TLSv1.2"); context.init(null, tmf.getTrustManagers(), null); return context; }
1.2 加載Java KeyStore
public SSLContext InitSSLContext() throws NoSuchAlgorithmException{ // 加載java keystore 倉庫 KeyStore caKs = KeyStore.getInstance("JKS"); // 把生成好的jks證書加載進(jìn)來 caKs.load(new FileInputStream(CA_PATH), PASSWORD.toCharArray()); // 把加載好的證書放入信任的列表 TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(caKs); // 最后創(chuàng)建SSLContext,將可信任證書列表傳入。 SSLContext context = SSLContext.getInstance("TLSv1.2"); context.init(null, tmf.getTrustManagers(), null); return context; }
二、連接服務(wù)器成功后,需要?jiǎng)?chuàng)建SSLEngine對(duì)象,并進(jìn)行相關(guān)設(shè)置與握手處理。
通過第一步生成的SSLContext創(chuàng)建SSLSocketFactory并將當(dāng)前的SocketChannel進(jìn)行綁定(注:很多別人的例子都沒有這步操作,如果只存在一個(gè)HTTPS的連接理論上沒有問題,但如果希望同時(shí)創(chuàng)建大量的HTTPS請(qǐng)求“可能”有問題,因?yàn)镾SLEngine內(nèi)部使用哪個(gè)Socket進(jìn)行操作數(shù)據(jù)是不確定,如果我的理解有誤歡迎指正)。
然后調(diào)用創(chuàng)建SSLEngine對(duì)象,并初始化操作數(shù)據(jù)的Buffer,然后開始進(jìn)入握手階段。(注:這里創(chuàng)建的Buffer主要用于將應(yīng)用層數(shù)據(jù)加密為網(wǎng)絡(luò)數(shù)據(jù),將網(wǎng)絡(luò)數(shù)據(jù)解密為應(yīng)用層數(shù)據(jù)使用:“密文與明文”)。
public final void OnConnected() { super.OnConnected(); // 設(shè)置socket,并創(chuàng)建SSLEngine,開始握手 SSLSocketFactory fx = mSSLContext.getSocketFactory(); // 這里將自己的channel傳進(jìn)去 fx.createSocket(mSocketChannel.GetSocket(), mHost, mPort, false); mSSLEngine = this.InitSSLEngine(mSSLContext); // 初始化使用的BUFFER int appBufSize = mSSLEngine.getSession().getApplicationBufferSize(); int netBufSize = mSSLEngine.getSession().getPacketBufferSize(); mAppDataBuf = ByteBuffer.allocate(appBufSize); mNetDataBuf = ByteBuffer.allocate(netBufSize); pAppDataBuf = ByteBuffer.allocate(appBufSize); pNetDataBuf = ByteBuffer.allocate(netBufSize); // 初始化完成,準(zhǔn)備開啟握手 mSSLInitiated = true; mSSLEngine.beginHandshake(); this.ProcessHandShake(null); }
三、進(jìn)行握手操作
下圖簡(jiǎn)單展示了握手流程,由客戶端發(fā)起,通過一些列的數(shù)據(jù)交換最終完成握手操作。要成功與服務(wù)器建立連接,握手流程是非常重要的環(huán)節(jié),幸好SSEngine內(nèi)部已經(jīng)實(shí)現(xiàn)了證書驗(yàn)證、交換等步驟,我們只需要在其上層執(zhí)行特定的行為(握手狀態(tài)處理)。
3.1 握手相關(guān)狀態(tài)(來自getHandshakeStatus方法)
NEED_WRAP當(dāng)前握手狀態(tài)表示需要加密數(shù)據(jù),即將要發(fā)送的應(yīng)用層數(shù)據(jù)加密輸出為網(wǎng)絡(luò)層數(shù)據(jù),并執(zhí)行發(fā)送操作。
NEED_UNWRAP當(dāng)前握手狀態(tài)表示需要對(duì)數(shù)據(jù)進(jìn)行解密,即將收到的網(wǎng)絡(luò)層數(shù)據(jù)解密后成應(yīng)用層數(shù)據(jù)。
NEED_TASK當(dāng)前握手狀態(tài)表示需要執(zhí)行任務(wù),因?yàn)橛行┎僮骺赡鼙容^耗時(shí),如果不希望造成阻塞流程就需要開啟異步任務(wù)進(jìn)行執(zhí)行。
FINISHED當(dāng)前握手已完成
NOT_HANDSHAKING表示不需要握手,這個(gè)主要是再次連接時(shí),為了加快速度而跳過握手流程。
3.2處理握手的方法
以下代碼展示了握手流程中的各種狀態(tài)的處理,主要的邏輯就是如果需要加密就執(zhí)行加密操作,如果需要執(zhí)行解密就執(zhí)行解密操作(廢話@_@!)。
protected void ProcessHandShake(SSLEngineResult result){ if(this.isClosed() || this.isShutdown()) return; // 區(qū)分是來此WRAP UNWRAP調(diào)用,還是其他調(diào)用 SSLEngineResult.HandshakeStatus status; if(result != null){ status = result.getHandshakeStatus(); }else{ status = mSSLEngine.getHandshakeStatus(); } switch(status) { // 需要加密 case NEED_WRAP: //判斷isOutboundDone,當(dāng)true時(shí),說明已經(jīng)不需要再處理任何的NEED_WRAP操作了. // 因?yàn)橐呀?jīng)顯式調(diào)用過closeOutbound,且就算執(zhí)行wrap, // SSLEngineReulst.STATUS也一定是CLOSED,沒有任何意義 if(mSSLEngine.isOutboundDone()){ // 如果還有數(shù)據(jù)則發(fā)送出去 if(mNetDataBuf.position() > 0) { mNetDataBuf.flip(); mSocketChannel.WriteAndFlush(mNetDataBuf); } break; } // 執(zhí)行加密流程 this.ProcessWrapEvent(); break; // 需要解密 case NEED_UNWRAP: //判斷inboundDone是否為true, true說明peer端發(fā)送了close_notify, // peer發(fā)送了close_notify也可能被unwrap操作捕獲到,結(jié)果就是返回的CLOSED if(mSSLEngine.isInboundDone()){ //peer端發(fā)送關(guān)閉,此時(shí)需要判斷是否調(diào)用closeOutbound if(mSSLEngine.isOutboundDone()){ return; } mSSLEngine.closeOutbound(); } break; case NEED_TASK: // 執(zhí)行異步任務(wù),我這里是同步執(zhí)行的,可以弄一個(gè)異步線程池進(jìn)行。 Runnable task = mSSLEngine.getDelegatedTask(); if(task != null){ task.run(); // executor.execute(task); 這樣使用異步也是可以的, //但是異步就需要對(duì)ProcessHandShake的調(diào)用做特殊處理,因?yàn)楫惒降?,像下面這直接是會(huì)導(dǎo)致瘋狂調(diào)用。 } this.ProcessHandShake(null); // 繼續(xù)處理握手 break; case FINISHED: // 握手完成 mHandshakeCompleted = true; this.OnHandCompleted(); return; case NOT_HANDSHAKING: // 不需要握手 if(!mHandshakeCompleted) { mHandshakeCompleted = true; this.OnHandCompleted(); } return; } }
四、數(shù)據(jù)的發(fā)送與接收
握手成功后就可以進(jìn)行正常的數(shù)據(jù)發(fā)送與接收,但是需要額外在數(shù)據(jù)發(fā)送的時(shí)候進(jìn)行加密操作,數(shù)據(jù)接收后進(jìn)行解密操作。
這里需要額外說明一下,在握手期間也是會(huì)需要讀取數(shù)據(jù)的,因?yàn)榉?wù)器發(fā)送過來的數(shù)據(jù)需要我們執(zhí)行讀取并解密操作。而這個(gè)操作在一些其他的例子中直接使用了阻塞的讀取方式,我這里則是放在OnRead事件調(diào)用后進(jìn)行處理,這樣才符合NIO模型。
4.1加密操作(SelectionKey.OP_WRITE)
protected void ProcessWrapEvent(){ if(this.isClosed() || this.isShutdown()) return; SSLEngineResult result = mSSLEngine.wrap(mAppDataBuf, mNetDataBuf); // 處理result if(ProcessSSLStatus(result, true)){ mNetDataBuf.flip(); mSocketChannel.WriteAndFlush(mNetDataBuf); // 發(fā)完成后清空buffer mNetDataBuf.clear(); } mAppDataBuf.clear(); // 如果沒有握手完成,則繼續(xù)調(diào)用握手處理 if(!mHandshakeCompleted) this.ProcessHandShake(result); }
4.2 解密操作(SelectionKey.OP_READ)
protected void ProcessUnWrapEvent(){ if(this.isClosed() || this.isShutdown()) return; do{ // 執(zhí)行解密操作 SSLEngineResult res = mSSLEngine.unwrap(pNetDataBuf, pAppDataBuf); if(!ProcessSSLStatus(res, false)) // 這里不需要對(duì)`pNetDataBuf`進(jìn)行處理,因?yàn)镻rocessSSLStatus里面已經(jīng)做好處理了。 return; if(res.getStatus() == Status.CLOSED) break; // 未完成握手時(shí),需要繼續(xù)調(diào)用握手處理 if(!mHandshakeCompleted) this.ProcessHandShake(res); }while(pNetDataBuf.hasRemaining()); // 數(shù)據(jù)都解密完了,這個(gè)就可以清空了。 if(!pNetDataBuf.hasRemaining()) pNetDataBuf.clear(); }
到此這篇關(guān)于Java通過SSLEngine與NIO實(shí)現(xiàn)HTTPS訪問的文章就介紹到這了,更多相關(guān)Java通過SSLEngine與NIO實(shí)現(xiàn)HTTPS訪問內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IDEA進(jìn)程已結(jié)束,退出代碼-1073741819 (0xC0000005)的bug
這篇文章主要介紹了IDEA進(jìn)程已結(jié)束,退出代碼-1073741819 (0xC0000005)的bug,本文通過實(shí)例代碼圖文的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04基于Spring MVC 簡(jiǎn)介及入門小例子(推薦)
下面小編就為大家?guī)硪黄赟pring MVC 簡(jiǎn)介及入門小例子(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06java線程池prestartCoreThread prestartAllCoreThreads的預(yù)熱源碼解讀
這篇文章主要介紹了java線程池prestartCoreThread prestartAllCoreThreads的預(yù)熱源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10