偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

用了這么久,你真的真的明白HttpClient的實現(xiàn)原理了嗎?

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

 一、背景

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

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

第一種在現(xiàn)實中的應(yīng)用范圍其實比想象中的要廣泛一些。雙方線下交換密鑰,客戶端在發(fā)送的數(shù)據(jù)采用的已經(jīng)是密文了,這個密文通過透明的Http協(xié)議在互聯(lián)網(wǎng)上傳輸。

服務(wù)端在接收到請求后,按照約定的方式解密獲得明文。這種內(nèi)容就算被劫持了也不要緊,因為第三方不知道他們的加解密方法。然而這種做法太特殊了,客戶端與服務(wù)端都需要關(guān)心這個加解密特殊邏輯。

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

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

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

二、加密方法

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

2.1 對稱加密

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

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

2.2 非對稱加密

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

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

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

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

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

2.3 混合加密

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

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

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

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

三、HTTPS握手

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

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

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

4.1 獲得SSL連接工廠以及域名校驗器

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

  1. public CloseableHttpClient build() {  
  2.         //省略部分代碼  
  3.         HttpClientConnectionManager connManagerCopy = this.connManager;  
  4.         //如果指定了連接池管理器則使用指定的,否則新建一個默認(rèn)的  
  5.         if (connManagerCopy == null) {  
  6.             LayeredConnectionSocketFactory sslSocketFactoryCopy = this.sslSocketFactory;  
  7.             if (sslSocketFactoryCopy == null) {  
  8.                 //如果開啟了使用環(huán)境變量,https版本與密碼控件從環(huán)境變量中讀取  
  9.                 final String[] supportedProtocols = systemProperties ? split(  
  10.                         System.getProperty("https.protocols")) : null;  
  11.                 final String[] supportedCipherSuites = systemProperties ? split(  
  12.                         System.getProperty("https.cipherSuites")) : null;  
  13.                 //如果沒有指定,使用默認(rèn)的域名驗證器,會根據(jù)ssl會話中服務(wù)端返回的證書來驗證與域名是否匹配  
  14.                 HostnameVerifier hostnameVerifierCopy = this.hostnameVerifier; 
  15.                  if (hostnameVerifierCopy == null) {  
  16.                     hostnameVerifierCopy = new DefaultHostnameVerifier(publicSuffixMatcherCopy);  
  17.                 }  
  18.                 //如果制定了SslContext則生成定制的SSL連接工廠,否則使用默認(rèn)的連接工廠  
  19.                 if (sslContext != null) {  
  20.                     sslSocketFactoryCopy = new SSLConnectionSocketFactory(  
  21.                             sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifierCopy);  
  22.                 } else {  
  23.                     if (systemProperties) {  
  24.                         sslSocketFactoryCopy = new SSLConnectionSocketFactory(  
  25.                                 (SSLSocketFactory) SSLSocketFactory.getDefault(),  
  26.                                 supportedProtocols, supportedCipherSuites, hostnameVerifierCopy);  
  27.                     } else {  
  28.                         sslSocketFactoryCopy = new SSLConnectionSocketFactory(  
  29.                                 SSLContexts.createDefault(),  
  30.                                 hostnameVerifierCopy);  
  31.                     }  
  32.                 }  
  33.             }  
  34.             //將Ssl連接工廠注冊到連接池管理器中,當(dāng)需要產(chǎn)生Https連接的時候,會根據(jù)上面的SSL連接工廠生產(chǎn)SSL連接  
  35.             @SuppressWarnings("resource")  
  36.             final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager(  
  37.                     RegistryBuilder.<ConnectionSocketFactory>create()  
  38.                         .register("http", PlainConnectionSocketFactory.getSocketFactory())  
  39.                         .register("https", sslSocketFactoryCopy)  
  40.                         .build(),  
  41.                     null,  
  42.                     null,  
  43.                     dnsResolver,  
  44.                     connTimeToLive,  
  45.                     connTimeToLiveTimeUnit != null ? connTimeToLiveTimeUnit : TimeUnit.MILLISECONDS);  
  46.             //省略部分代碼  
  47.     }  

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

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

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

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

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

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

4.2 如何獲得SSL連接

如何從連接池中獲得一個連接,這個過程之前的文章中有分析過,這里不做分析,參考連接:

http://www.cnblogs.com/kingszelda/p/8988505.html。

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

DefaultHttpClientConnectionOperator部分的代碼為: 

  1. public void connect(  
  2.             final ManagedHttpClientConnection conn,  
  3.             final HttpHost host,  
  4.             final InetSocketAddress localAddress,  
  5.             final int connectTimeout,  
  6.             final SocketConfig socketConfig,  
  7.             final HttpContext context) throws IOException {  
  8.         //之前在HttpClientBuilder中register了http與https不同的連接池實現(xiàn),這里lookup獲得Https的實現(xiàn),即SSLConnectionSocketFactory   
  9.         final Lookup<ConnectionSocketFactory> registry = getSocketFactoryRegistry(context);  
  10.         final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName());  
  11.         if (sf == null) {  
  12.             throw new UnsupportedSchemeException(host.getSchemeName() +  
  13.                     " protocol is not supported");  
  14.         }  
  15.         //如果是ip形式的地址可以直接使用,否則使用dns解析器解析得到域名對應(yīng)的ip  
  16.         final InetAddress[] addresses = host.getAddress() != null ? 
  17.                  new InetAddress[] { host.getAddress() } : this.dnsResolver.resolve(host.getHostName());  
  18.         final int port = this.schemePortResolver.resolve(host);  
  19.         //一個域名可能對應(yīng)多個Ip,按照順序嘗試連接  
  20.         for (int i = 0; i < addresses.length; i++) {  
  21.             final InetAddress address = addresses[i];  
  22.             final boolean last = i == addresses.length - 1;  
  23.             //這里只是生成一個socket,還并沒有連接  
  24.             Socket sock = sf.createSocket(context);  
  25.             //設(shè)置一些tcp層的參數(shù) 
  26.              sock.setSoTimeout(socketConfig.getSoTimeout());  
  27.             sock.setReuseAddress(socketConfig.isSoReuseAddress());  
  28.             sock.setTcpNoDelay(socketConfig.isTcpNoDelay());  
  29.             sock.setKeepAlive(socketConfig.isSoKeepAlive());  
  30.             if (socketConfig.getRcvBufSize() > 0) { 
  31.                  sock.setReceiveBufferSize(socketConfig.getRcvBufSize());  
  32.             }  
  33.             if (socketConfig.getSndBufSize() > 0) {  
  34.                 sock.setSendBufferSize(socketConfig.getSndBufSize());  
  35.             }  
  36.             final int linger = socketConfig.getSoLinger();  
  37.             if (linger >= 0) {  
  38.                 sock.setSoLinger(true, linger);  
  39.             }  
  40.             conn.bind(sock);  
  41.             final InetSocketAddress remoteAddress = new InetSocketAddress(address, port);  
  42.             if (this.log.isDebugEnabled()) {  
  43.                 this.log.debug("Connecting to " + remoteAddress);  
  44.             }  
  45.             try {  
  46.                 //通過SSLConnectionSocketFactory建立連接并綁定到conn上  
  47.                 sock = sf.connectSocket(  
  48.                         connectTimeout, sock, host, remoteAddress, localAddress, context);  
  49.                 conn.bind(sock);  
  50.                 if (this.log.isDebugEnabled()) {  
  51.                     this.log.debug("Connection established " + conn);  
  52.                 }  
  53.                 return;  
  54.             }   
  55.             //省略一些代碼  
  56.         }  
  57.     } 

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

SSLConnectionSocketFactory部分源碼如下: 

  1. @Override  
  2.     public Socket connectSocket(  
  3.             final int connectTimeout,  
  4.             final Socket socket,  
  5.             final HttpHost host,  
  6.             final InetSocketAddress remoteAddress,  
  7.             final InetSocketAddress localAddress,  
  8.             final HttpContext context) throws IOException {  
  9.         Args.notNull(host, "HTTP host");  
  10.         Args.notNull(remoteAddress, "Remote address");  
  11.         final Socket sock = socket != null ? socket : createSocket(context);  
  12.         if (localAddress != null) {  
  13.             sock.bind(localAddress);  
  14.         }  
  15.         try {  
  16.             if (connectTimeout > 0 && sock.getSoTimeout() == 0) {  
  17.                 sock.setSoTimeout(connectTimeout);  
  18.             }  
  19.             if (this.log.isDebugEnabled()) {  
  20.                 this.log.debug("Connecting socket to " + remoteAddress + " with timeout " + connectTimeout);  
  21.             }  
  22.             //建立連接  
  23.             sock.connect(remoteAddress, connectTimeout);  
  24.         } catch (final IOException ex) {  
  25.             try {  
  26.                 sock.close();  
  27.             } catch (final IOException ignore) {  
  28.             }  
  29.             throw ex;  
  30.         }  
  31.         // 如果當(dāng)前是SslSocket則進(jìn)行SSL握手與域名校驗  
  32.         if (sock instanceof SSLSocket) {  
  33.             final SSLSocket sslsock = (SSLSocket) sock;  
  34.             this.log.debug("Starting handshake");  
  35.             sslsock.startHandshake();  
  36.             verifyHostname(sslsock, host.getHostName());  
  37.             return sock;  
  38.         } else {  
  39.             //如果不是SslSocket則將其包裝為SslSocket  
  40.             return createLayeredSocket(sock, host.getHostName(), remoteAddress.getPort(), context);  
  41.         }  
  42.     }  
  43.     @Override  
  44.     public Socket createLayeredSocket(  
  45.             final Socket socket,  
  46.             final String target,  
  47.             final int port,  
  48.             final HttpContext context) throws IOException {  
  49.             //將普通socket包裝為SslSocket,socketfactory是根據(jù)HttpClientBuilder中的SSLContext生成的,其中包含密鑰信息  
  50.         final SSLSocket sslsock = (SSLSocket) this.socketfactory.createSocket(  
  51.                 socket,  
  52.                 target,  
  53.                 port,  
  54.                 true);  
  55.         //如果制定了SSL層協(xié)議版本與加密算法,則使用指定的,否則使用默認(rèn)的  
  56.         if (supportedProtocols != null) {  
  57.             sslsock.setEnabledProtocols(supportedProtocols);  
  58.         } else {  
  59.             // If supported protocols are not explicitly set, remove all SSL protocol versions 
  60.             final String[] allProtocols = sslsock.getEnabledProtocols();  
  61.             final List<String> enabledProtocols = new ArrayList<String>(allProtocols.length);  
  62.             for (final String protocol: allProtocols) {  
  63.                 if (!protocol.startsWith("SSL")) {  
  64.                     enabledProtocols.add(protocol);  
  65.                 }  
  66.             }  
  67.             if (!enabledProtocols.isEmpty()) {  
  68.                 sslsock.setEnabledProtocols(enabledProtocols.toArray(new String[enabledProtocols.size()]));  
  69.             }  
  70.         }  
  71.         if (supportedCipherSuites != null) {  
  72.             sslsock.setEnabledCipherSuites(supportedCipherSuites);  
  73.         }  
  74.         if (this.log.isDebugEnabled()) { 
  75.              this.log.debug("Enabled protocols: " + Arrays.asList(sslsock.getEnabledProtocols()));  
  76.             this.log.debug("Enabled cipher suites:" + Arrays.asList(sslsock.getEnabledCipherSuites()));  
  77.         }  
  78.         prepareSocket(sslsock);  
  79.         this.log.debug("Starting handshake");  
  80.         //Ssl連接握手  
  81.         sslsock.startHandshake();  
  82.         //握手成功后校驗返回的證書與域名是否一致  
  83.         verifyHostname(sslsock, target);  
  84.         return sslsock; 
  85.      } 

可以看到,對于一個SSL通信而言。首先是建立普通socket連接,然后進(jìn)行ssl握手,之后驗證證書與域名一致性。之后的操作就是通過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é)商密鑰的時候使用非對稱加密,密鑰協(xié)商結(jié)束后使用對稱加密
  3.  有些場景下,即使通過了https進(jìn)行了加解密,業(yè)務(wù)系統(tǒng)也會對報文進(jìn)行二次加密與簽名
  4.  HttpClient在build的時候,連接池管理器注冊了兩個SslSocketFactory,用來匹配http或者h(yuǎn)ttps字符串
  5.  https對應(yīng)的socket建立原則是先建立,后驗證域名與證書一致性
  6.  ssl層加解密由jdk自身完成,不需要httpClient進(jìn)行額外操作 

 

責(zé)任編輯:龐桂玉 來源: Java知音
相關(guān)推薦

2021-07-21 10:10:14

require前端代碼

2021-11-08 10:00:19

require前端模塊

2019-11-27 10:54:43

Tomcat連接數(shù)線程池

2019-12-04 12:33:48

程序員技術(shù)設(shè)計

2018-06-08 10:12:10

Web緩存體系服務(wù)器

2022-01-25 12:41:31

ChromeResponse接口

2022-02-08 13:39:35

LinuxUNIX系統(tǒng)

2024-12-10 13:00:00

C++引用

2019-08-05 15:05:35

2018-01-31 10:24:45

熱插拔原理服務(wù)器

2021-05-28 06:16:28

藍(lán)牙Wi-FiNFC

2020-12-01 10:18:16

RabbitMQ

2013-07-15 16:55:45

2020-02-15 15:33:55

Python如何運(yùn)作

2020-06-29 08:32:21

高并發(fā)程序員流量

2022-04-07 08:20:22

typeinterface前端

2020-09-18 06:39:18

hashMap循環(huán)數(shù)據(jù)

2021-08-18 15:23:42

SDNSD-WAN軟件定義網(wǎng)絡(luò)

2022-06-22 13:06:48

物聯(lián)網(wǎng)數(shù)字化轉(zhuǎn)型

2009-05-18 10:57:35

.NETString特性
點贊
收藏

51CTO技術(shù)棧公眾號