自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

 一、背景

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

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

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

服務(wù)端在接收到請(qǐng)求后,按照約定的方式解密獲得明文。這種內(nèi)容就算被劫持了也不要緊,因?yàn)榈谌讲恢浪麄兊募咏饷芊椒?。然而這種做法太特殊了,客戶(hù)端與服務(wù)端都需要關(guān)心這個(gè)加解密特殊邏輯。

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

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

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

二、加密方法

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

2.1 對(duì)稱(chēng)加密

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

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

2.2 非對(duì)稱(chēng)加密

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

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

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

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

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

2.3 混合加密

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

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

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

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

三、HTTPS握手

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

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

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

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

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

  1. public CloseableHttpClient build() {  
  2.         //省略部分代碼  
  3.         HttpClientConnectionManager connManagerCopy = this.connManager;  
  4.         //如果指定了連接池管理器則使用指定的,否則新建一個(gè)默認(rèn)的  
  5.         if (connManagerCopy == null) {  
  6.             LayeredConnectionSocketFactory sslSocketFactoryCopy = this.sslSocketFactory;  
  7.             if (sslSocketFactoryCopy == null) {  
  8.                 //如果開(kāi)啟了使用環(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.                 //如果沒(méi)有指定,使用默認(rèn)的域名驗(yàn)證器,會(huì)根據(jù)ssl會(huì)話(huà)中服務(wù)端返回的證書(shū)來(lái)驗(yàn)證與域名是否匹配  
  14.                 HostnameVerifier hostnameVerifierCopy = this.hostnameVerifier; 
  15.                  if (hostnameVerifierCopy == null) {  
  16.                     hostnameVerifierCopy = new DefaultHostnameVerifier(publicSuffixMatcherCopy);  
  17.                 }  
  18.                 //如果制定了SslContext則生成定制的SSL連接工廠(chǎng),否則使用默認(rèn)的連接工廠(chǎng)  
  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連接工廠(chǎng)注冊(cè)到連接池管理器中,當(dāng)需要產(chǎn)生Https連接的時(shí)候,會(huì)根據(jù)上面的SSL連接工廠(chǎng)生產(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.     }  

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

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

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

注意,這里有一些區(qū)別,BrowserCompatHostnameVerifier可以匹配多級(jí)子域名,"*.foo.com"可以匹配"a.b.foo.com"。StrictHostnameVerifier不能匹配多級(jí)子域名,只能到"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)是不支持多級(jí)子域名匹配策略的。

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

4.2 如何獲得SSL連接

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

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

在從連接池中獲得一個(gè)連接后,如果這個(gè)連接不處于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不同的連接池實(shí)現(xiàn),這里lookup獲得Https的實(shí)現(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解析器解析得到域名對(duì)應(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.         //一個(gè)域名可能對(duì)應(yīng)多個(gè)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.             //這里只是生成一個(gè)socket,還并沒(méi)有連接  
  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.                 //通過(guò)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握手與域名校驗(yàn)  
  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.         //握手成功后校驗(yàn)返回的證書(shū)與域名是否一致  
  83.         verifyHostname(sslsock, target);  
  84.         return sslsock; 
  85.      } 

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

五、本文總結(jié)

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

 

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

2021-11-08 10:00:19

require前端模塊

2021-07-21 10:10:14

require前端代碼

2019-11-27 10:54:43

Tomcat連接數(shù)線(xiàn)程池

2018-06-08 10:12:10

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

2019-12-04 12:33:48

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

2022-02-08 13:39:35

LinuxUNIX系統(tǒng)

2022-01-25 12:41:31

ChromeResponse接口

2024-12-10 13:00:00

C++引用

2018-01-31 10:24:45

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

2021-05-28 06:16:28

藍(lán)牙Wi-FiNFC

2019-08-05 15:05:35

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ā)程序員流量

2020-09-18 06:39:18

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

2022-04-07 08:20:22

typeinterface前端

2020-10-20 07:49:00

JWT(JSON We

2024-12-04 09:41:06

2021-08-18 15:23:42

SDNSD-WAN軟件定義網(wǎng)絡(luò)
點(diǎn)贊
收藏

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