<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Https協(xié)議與HttpClient的實(shí)現(xiàn)

          共 10809字,需瀏覽 22分鐘

           ·

          2020-12-11 20:36

          來源:https://www.cnblogs.com/kingszelda/p/9029735.html

          一、背景

          HTTP是一個(gè)傳輸內(nèi)容有可讀性的公開協(xié)議,客戶端與服務(wù)器端的數(shù)據(jù)完全通過明文傳輸。在這個(gè)背景之下,整個(gè)依賴于Http協(xié)議的互聯(lián)網(wǎng)數(shù)據(jù)都是透明的,這帶來了很大的數(shù)據(jù)安全隱患。想要解決這個(gè)問題有兩個(gè)思路:

          1. C/S端各自負(fù)責(zé),即客戶端與服務(wù)端使用協(xié)商好的加密內(nèi)容在Http上通信
          2. C/S端不負(fù)責(zé)加解密,加解密交給通信協(xié)議本身解決

          第一種在現(xiàn)實(shí)中的應(yīng)用范圍其實(shí)比想象中的要廣泛一些。雙方線下交換密鑰,客戶端在發(fā)送的數(shù)據(jù)采用的已經(jīng)是密文了,這個(gè)密文通過透明的Http協(xié)議在互聯(lián)網(wǎng)上傳輸。服務(wù)端在接收到請求后,按照約定的方式解密獲得明文。這種內(nèi)容就算被劫持了也不要緊,因?yàn)榈谌讲恢浪麄兊募咏饷芊椒?。然而這種做法太特殊了,客戶端與服務(wù)端都需要關(guān)心這個(gè)加解密特殊邏輯。

          第二種C/S端可以不關(guān)心上面的特殊邏輯,他們認(rèn)為發(fā)送與接收的都是明文,因?yàn)榧咏饷苓@一部分已經(jīng)被協(xié)議本身處理掉了。

          從結(jié)果上看這兩種方案似乎沒有什么區(qū)別,但是從軟件工程師的角度看區(qū)別非常巨大。因?yàn)榈谝环N需要業(yè)務(wù)系統(tǒng)自己開發(fā)響應(yīng)的加解密功能,并且線下要交互密鑰,第二種沒有開發(fā)量。

          HTTPS是當(dāng)前最流行的HTTP的安全形式,由NetScape公司首創(chuàng)。在HTTPS中,URL都是以https://開頭,而不是http://。使用了HTTPS時(shí),所有的HTTP的請求與響應(yīng)在發(fā)送到網(wǎng)絡(luò)上之前都進(jìn)行了加密,這是通過在SSL層實(shí)現(xiàn)的。

          二、加密方法

          通過SSL層對明文數(shù)據(jù)進(jìn)行加密,然后放到互聯(lián)網(wǎng)上傳輸,這解決了HTTP協(xié)議原本的數(shù)據(jù)安全性問題。一般來說,對數(shù)據(jù)加密的方法分為對稱加密與非對稱加密。

          2.1 對稱加密

          對稱加密是指加密與解密使用同樣的密鑰,常見的算法有DES與AES等,算法時(shí)間與密鑰長度相關(guān)。

          對稱密鑰最大的缺點(diǎn)是需要維護(hù)大量的對稱密鑰,并且需要線下交換。加入一個(gè)網(wǎng)絡(luò)中有n個(gè)實(shí)體,則需要n(n-1)個(gè)密鑰。

          2.2 非對稱加密

          非對稱加密是指基于公私鑰(public/private key)的加密方法,常見算法有RSA,一般而言加密速度慢于對稱加密。

          對稱加密比非對稱加密多了一個(gè)步驟,即要獲得服務(wù)端公鑰,而不是各自維護(hù)的密鑰。

          整個(gè)加密算法建立在一定的數(shù)論基礎(chǔ)上運(yùn)算,達(dá)到的效果是,加密結(jié)果不可逆。即只有通過私鑰(private key)才能解密得到經(jīng)由公鑰(public key)加密的密文。

          在這種算法下,整個(gè)網(wǎng)絡(luò)中的密鑰數(shù)量大大降低,每個(gè)人只需要維護(hù)一對公司鑰即可。即n個(gè)實(shí)體的網(wǎng)絡(luò)中,密鑰個(gè)數(shù)是2n。

          其缺點(diǎn)是運(yùn)行速度慢。

          2.3 混合加密

          周星馳電影《食神》中有一個(gè)場景,黑社會(huì)火并,爭論撒尿蝦與牛丸的底盤劃分問題。食神說:“真是麻煩,摻在一起做成撒尿牛丸那,笨蛋!”

          對稱加密的優(yōu)點(diǎn)是速度快,缺點(diǎn)是需要交換密鑰。非對稱加密的優(yōu)點(diǎn)是不需要交互密鑰,缺點(diǎn)是速度慢。干脆摻在一起用好了。

          混合加密正是HTTPS協(xié)議使用的加密方式。先通過非對稱加密交換對稱密鑰,后通過對稱密鑰進(jìn)行數(shù)據(jù)傳輸。

          由于數(shù)據(jù)傳輸?shù)牧窟h(yuǎn)遠(yuǎn)大于建立連接初期交換密鑰時(shí)使用非對稱加密的數(shù)據(jù)量,所以非對稱加密帶來的性能影響基本可以忽略,同時(shí)又提高了效率。

          三、HTTPS握手

          可以看到,在原HTTP協(xié)議的基礎(chǔ)上,HTTPS加入了安全層處理:

          1. 客戶端與服務(wù)端交換證書并驗(yàn)證身份,現(xiàn)實(shí)中服務(wù)端很少驗(yàn)證客戶端的證書
          2. 協(xié)商加密協(xié)議的版本與算法,這里可能出現(xiàn)版本不匹配導(dǎo)致失敗
          3. 協(xié)商對稱密鑰,這個(gè)過程使用非對稱加密進(jìn)行
          4. 將HTTP發(fā)送的明文使用3中的密鑰,2中的加密算法加密得到密文
          5. TCP層正常傳輸,對HTTPS無感知

          四、HttpClient對HTTPS協(xié)議的支持

          4.1 獲得SSL連接工廠以及域名校驗(yàn)器

          作為一名軟件工程師,我們關(guān)心的是“HTTPS協(xié)議”在代碼上是怎么實(shí)現(xiàn)的呢?探索HttpClient源碼的奧秘,一切都要從HttpClientBuilder開始。

          public?CloseableHttpClient?build()?{
          ????????//省略部分代碼
          ????????HttpClientConnectionManager?connManagerCopy?=?this.connManager;
          ????????//如果指定了連接池管理器則使用指定的,否則新建一個(gè)默認(rèn)的
          ????????if?(connManagerCopy?==?null)?{
          ????????????LayeredConnectionSocketFactory?sslSocketFactoryCopy?=?this.sslSocketFactory;
          ????????????if?(sslSocketFactoryCopy?==?null)?{
          ????????????????//如果開啟了使用環(huán)境變量,https版本與密碼控件從環(huán)境變量中讀取
          ????????????????final?String[]?supportedProtocols?=?systemProperties???split(
          ????????????????????????System.getProperty("https.protocols"))?:?null;
          ????????????????final?String[]?supportedCipherSuites?=?systemProperties???split(
          ????????????????????????System.getProperty("https.cipherSuites"))?:?null;
          ????????????????//如果沒有指定,使用默認(rèn)的域名驗(yàn)證器,會(huì)根據(jù)ssl會(huì)話中服務(wù)端返回的證書來驗(yàn)證與域名是否匹配
          ????????????????HostnameVerifier?hostnameVerifierCopy?=?this.hostnameVerifier;
          ????????????????if?(hostnameVerifierCopy?==?null)?{
          ????????????????????hostnameVerifierCopy?=?new?DefaultHostnameVerifier(publicSuffixMatcherCopy);
          ????????????????}
          ????????????????//如果制定了SslContext則生成定制的SSL連接工廠,否則使用默認(rèn)的連接工廠
          ????????????????if?(sslContext?!=?null)?{
          ????????????????????sslSocketFactoryCopy?=?new?SSLConnectionSocketFactory(
          ????????????????????????????sslContext,?supportedProtocols,?supportedCipherSuites,?hostnameVerifierCopy);
          ????????????????}?else?{
          ????????????????????if?(systemProperties)?{
          ????????????????????????sslSocketFactoryCopy?=?new?SSLConnectionSocketFactory(
          ????????????????????????????????(SSLSocketFactory)?SSLSocketFactory.getDefault(),
          ????????????????????????????????supportedProtocols,?supportedCipherSuites,?hostnameVerifierCopy);
          ????????????????????}?else?{
          ????????????????????????sslSocketFactoryCopy?=?new?SSLConnectionSocketFactory(
          ????????????????????????????????SSLContexts.createDefault(),
          ????????????????????????????????hostnameVerifierCopy);
          ????????????????????}
          ????????????????}
          ????????????}
          ????????????//將Ssl連接工廠注冊到連接池管理器中,當(dāng)需要產(chǎn)生Https連接的時(shí)候,會(huì)根據(jù)上面的SSL連接工廠生產(chǎn)SSL連接
          ????????????@SuppressWarnings("resource")
          ????????????final?PoolingHttpClientConnectionManager?poolingmgr?=?new?PoolingHttpClientConnectionManager(
          ????????????????????RegistryBuilder.create()
          ????????????????????????.register("http",?PlainConnectionSocketFactory.getSocketFactory())
          ????????????????????????.register("https",?sslSocketFactoryCopy)
          ????????????????????????.build(),
          ????????????????????null,
          ????????????????????null,
          ????????????????????dnsResolver,
          ????????????????????connTimeToLive,
          ????????????????????connTimeToLiveTimeUnit?!=?null???connTimeToLiveTimeUnit?:?TimeUnit.MILLISECONDS);
          ????????????//省略部分代碼
          ????}
          }

          上面的代碼將一個(gè)Ssl連接工廠SSLConnectionSocketFactory創(chuàng)建,并注冊到了連接池管理器中,供之后生產(chǎn)Ssl連接使用。連接池的問題參考:http://www.cnblogs.com/kingszelda/p/8988505.html

          這里在配置SSLConnectionSocketFactory時(shí)用到了幾個(gè)關(guān)鍵的組件,域名驗(yàn)證器HostnameVerifier以及上下文SSLContext。

          其中HostnameVerifier用來驗(yàn)證服務(wù)端證書與域名是否匹配,有多種實(shí)現(xiàn),DefaultHostnameVerifier采用的是默認(rèn)的校驗(yàn)規(guī)則,替代了之前版本中的BrowserCompatHostnameVerifier與StrictHostnameVerifier。NoopHostnameVerifier替代了AllowAllHostnameVerifier,采用的是不驗(yàn)證域名的策略。

          注意,這里有一些區(qū)別,BrowserCompatHostnameVerifier可以匹配多級子域名,"*.foo.com"可以匹配"a.b.foo.com"。StrictHostnameVerifier不能匹配多級子域名,只能到"a.foo.com"。

          而4.4之后的HttpClient使用了新的DefaultHostnameVerifier替換了上面的兩種策略,只保留了一種嚴(yán)格策略及StrictHostnameVerifier。因?yàn)閲?yán)格策略是IE6與JDK本身的策略,非嚴(yán)格策略是curl與firefox的策略。即默認(rèn)的HttpClient實(shí)現(xiàn)是不支持多級子域名匹配策略的。

          SSLContext存放的是和密鑰有關(guān)的關(guān)鍵信息,這部分與業(yè)務(wù)直接相關(guān),非常重要,這個(gè)放在后面單獨(dú)分析。

          4.2 如何獲得SSL連接

          如何從連接池中獲得一個(gè)連接,這個(gè)過程之前的文章中有分析過,這里不做分析,參考連接:http://www.cnblogs.com/kingszelda/p/8988505.html。

          在從連接池中獲得一個(gè)連接后,如果這個(gè)連接不處于establish狀態(tài),就需要先建立連接。

          DefaultHttpClientConnectionOperator部分的代碼為:

          public?void?connect(
          ????????final?ManagedHttpClientConnection?conn,
          ????????final?HttpHost?host,
          ????????final?InetSocketAddress?localAddress,
          ????????final?int?connectTimeout,
          ????????final?SocketConfig?socketConfig,
          ????????final?HttpContext?context)?throws?IOException?{
          ????//之前在HttpClientBuilder中register了http與https不同的連接池實(shí)現(xiàn),這里lookup獲得Https的實(shí)現(xiàn),即SSLConnectionSocketFactory????
          ????final?Lookup?registry?=?getSocketFactoryRegistry(context);
          ????final?ConnectionSocketFactory?sf?=?registry.lookup(host.getSchemeName());
          ????if?(sf?==?null)?{
          ????????throw?new?UnsupportedSchemeException(host.getSchemeName()?+
          ????????????????"?protocol?is?not?supported");
          ????}
          ????//如果是ip形式的地址可以直接使用,否則使用dns解析器解析得到域名對應(yīng)的ip
          ????final?InetAddress[]?addresses?=?host.getAddress()?!=?null??
          ????????????new?InetAddress[]?{?host.getAddress()?}?:?this.dnsResolver.resolve(host.getHostName());
          ????final?int?port?=?this.schemePortResolver.resolve(host);
          ????//一個(gè)域名可能對應(yīng)多個(gè)Ip,按照順序嘗試連接
          ????for?(int?i?=?0;?i?????????final?InetAddress?address?=?addresses[i];
          ????????final?boolean?last?=?i?==?addresses.length?-?1;
          ????????//這里只是生成一個(gè)socket,還并沒有連接
          ????????Socket?sock?=?sf.createSocket(context);
          ????????//設(shè)置一些tcp層的參數(shù)
          ????????sock.setSoTimeout(socketConfig.getSoTimeout());
          ????????sock.setReuseAddress(socketConfig.isSoReuseAddress());
          ????????sock.setTcpNoDelay(socketConfig.isTcpNoDelay());
          ????????sock.setKeepAlive(socketConfig.isSoKeepAlive());
          ????????if?(socketConfig.getRcvBufSize()?>?0)?{
          ????????????sock.setReceiveBufferSize(socketConfig.getRcvBufSize());
          ????????}
          ????????if?(socketConfig.getSndBufSize()?>?0)?{
          ????????????sock.setSendBufferSize(socketConfig.getSndBufSize());
          ????????}

          ????????final?int?linger?=?socketConfig.getSoLinger();
          ????????if?(linger?>=?0)?{
          ????????????sock.setSoLinger(true,?linger);
          ????????}
          ????????conn.bind(sock);

          ????????final?InetSocketAddress?remoteAddress?=?new?InetSocketAddress(address,?port);
          ????????if?(this.log.isDebugEnabled())?{
          ????????????this.log.debug("Connecting?to?"?+?remoteAddress);
          ????????}
          ????????try?{
          ????????????//通過SSLConnectionSocketFactory建立連接并綁定到conn上
          ????????????sock?=?sf.connectSocket(
          ????????????????????connectTimeout,?sock,?host,?remoteAddress,?localAddress,?context);
          ????????????conn.bind(sock);
          ????????????if?(this.log.isDebugEnabled())?{
          ????????????????this.log.debug("Connection?established?"?+?conn);
          ????????????}
          ????????????return;
          ????????}?
          ????????//省略一些代碼
          ????}
          }

          在上面的代碼中,我們看到了是建立SSL連接之前的準(zhǔn)備工作,這是通用流程,普通HTTP連接也一樣。SSL連接的特殊流程體現(xiàn)在哪里呢?

          SSLConnectionSocketFactory部分源碼如下:

          @Override
          public?Socket?connectSocket(
          ????????final?int?connectTimeout,
          ????????final?Socket?socket,
          ????????final?HttpHost?host,
          ????????final?InetSocketAddress?remoteAddress,
          ????????final?InetSocketAddress?localAddress,
          ????????final?HttpContext?context)
          ?throws?IOException?
          {
          ????Args.notNull(host,?"HTTP?host");
          ????Args.notNull(remoteAddress,?"Remote?address");
          ????final?Socket?sock?=?socket?!=?null???socket?:?createSocket(context);
          ????if?(localAddress?!=?null)?{
          ????????sock.bind(localAddress);
          ????}
          ????try?{
          ????????if?(connectTimeout?>?0?&&?sock.getSoTimeout()?==?0)?{
          ????????????sock.setSoTimeout(connectTimeout);
          ????????}
          ????????if?(this.log.isDebugEnabled())?{
          ????????????this.log.debug("Connecting?socket?to?"?+?remoteAddress?+?"?with?timeout?"?+?connectTimeout);
          ????????}
          ????????//建立連接
          ????????sock.connect(remoteAddress,?connectTimeout);
          ????}?catch?(final?IOException?ex)?{
          ????????try?{
          ????????????sock.close();
          ????????}?catch?(final?IOException?ignore)?{
          ????????}
          ????????throw?ex;
          ????}
          ????//?如果當(dāng)前是SslSocket則進(jìn)行SSL握手與域名校驗(yàn)
          ????if?(sock?instanceof?SSLSocket)?{
          ????????final?SSLSocket?sslsock?=?(SSLSocket)?sock;
          ????????this.log.debug("Starting?handshake");
          ????????sslsock.startHandshake();
          ????????verifyHostname(sslsock,?host.getHostName());
          ????????return?sock;
          ????}?else?{
          ????????//如果不是SslSocket則將其包裝為SslSocket
          ????????return?createLayeredSocket(sock,?host.getHostName(),?remoteAddress.getPort(),?context);
          ????}
          }

          @Override
          public?Socket?createLayeredSocket(
          ????????final?Socket?socket,
          ????????final?String?target,
          ????????final?int?port,
          ????????final?HttpContext?context)
          ?throws?IOException?
          {
          ????????//將普通socket包裝為SslSocket,socketfactory是根據(jù)HttpClientBuilder中的SSLContext生成的,其中包含密鑰信息
          ????final?SSLSocket?sslsock?=?(SSLSocket)?this.socketfactory.createSocket(
          ????????????socket,
          ????????????target,
          ????????????port,
          ????????????true);
          ????//如果制定了SSL層協(xié)議版本與加密算法,則使用指定的,否則使用默認(rèn)的
          ????if?(supportedProtocols?!=?null)?{
          ????????sslsock.setEnabledProtocols(supportedProtocols);
          ????}?else?{
          ????????//?If?supported?protocols?are?not?explicitly?set,?remove?all?SSL?protocol?versions
          ????????final?String[]?allProtocols?=?sslsock.getEnabledProtocols();
          ????????final?List?enabledProtocols?=?new?ArrayList(allProtocols.length);
          ????????for?(final?String?protocol:?allProtocols)?{
          ????????????if?(!protocol.startsWith("SSL"))?{
          ????????????????enabledProtocols.add(protocol);
          ????????????}
          ????????}
          ????????if?(!enabledProtocols.isEmpty())?{
          ????????????sslsock.setEnabledProtocols(enabledProtocols.toArray(new?String[enabledProtocols.size()]));
          ????????}
          ????}
          ????if?(supportedCipherSuites?!=?null)?{
          ????????sslsock.setEnabledCipherSuites(supportedCipherSuites);
          ????}

          ????if?(this.log.isDebugEnabled())?{
          ????????this.log.debug("Enabled?protocols:?"?+?Arrays.asList(sslsock.getEnabledProtocols()));
          ????????this.log.debug("Enabled?cipher?suites:"?+?Arrays.asList(sslsock.getEnabledCipherSuites()));
          ????}

          ????prepareSocket(sslsock);
          ????this.log.debug("Starting?handshake");
          ????//Ssl連接握手
          ????sslsock.startHandshake();
          ????//握手成功后校驗(yàn)返回的證書與域名是否一致
          ????verifyHostname(sslsock,?target);
          ????return?sslsock;
          }

          可以看到,對于一個(gè)SSL通信而言。首先是建立普通socket連接,然后進(jìn)行ssl握手,之后驗(yàn)證證書與域名一致性。之后的操作就是通過SSLSocketImpl進(jìn)行通信,協(xié)議細(xì)節(jié)在SSLSocketImpl類中體現(xiàn),但這部分代碼jdk并沒有開源,感興趣的可以下載相應(yīng)的openJdk源碼繼續(xù)分析。

          五、本文總結(jié)

          1. https協(xié)議是http的安全版本,做到了傳輸層數(shù)據(jù)的安全,但對服務(wù)器cpu有額外消耗
          2. https協(xié)議在協(xié)商密鑰的時(shí)候使用非對稱加密,密鑰協(xié)商結(jié)束后使用對稱加密
          3. 有些場景下,即使通過了https進(jìn)行了加解密,業(yè)務(wù)系統(tǒng)也會(huì)對報(bào)文進(jìn)行二次加密與簽名
          4. HttpClient在build的時(shí)候,連接池管理器注冊了兩個(gè)SslSocketFactory,用來匹配http或者h(yuǎn)ttps字符串
          5. https對應(yīng)的socket建立原則是先建立,后驗(yàn)證域名與證書一致性
          6. ssl層加解密由jdk自身完成,不需要httpClient進(jìn)行額外操作


          —————END—————



          喜歡本文的朋友,歡迎關(guān)注公眾號?程序員哆啦A夢,收看更多精彩內(nèi)容

          點(diǎn)個(gè)[在看],是對小達(dá)最大的支持!


          如果覺得這篇文章還不錯(cuò),來個(gè)【分享、點(diǎn)贊、在看】三連吧,讓更多的人也看到~

          瀏覽 77
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  aanquye | www.男人天堂 | 137无码XXXX肉体裸交摄影XXX | 天天草天天日天天干天天舔 | 亚洲vo1 |