1.后台HTTP是一种开放的协议,传输内容可读,客户端与服务器之间的数据完全以明文形式传输。在此背景下,依赖于Http协议的整个互联网数据是透明的,带来了极大的数据安全隐患。解决这个问题有两种方式:C/S端各自负责,即客户端和服务端使用协商好的加密内容在HTTP上进行通信。C/S端不负责加解密,加解密交给通信协议自己完成。第一种在现实中的适用范围其实比想象的要广。双方离线交换密钥,客户端发送的数据已经是密文,而这个密文是通过透明的Http协议在互联网上传输的。服务器收到请求后,按照约定的方法对明文进行解密。这种内容即使被劫持也没关系,因为第三方不知道他们的加解密方式。但是这种方式过于特殊,客户端和服务端都需要关心这种特殊的加解密逻辑。第二种C/S端可能不会关心上面的特殊逻辑,他们认为发送和接收的都是明文,因为加解密部分已经由协议自己处理了。从结果上看,这两种解决方案似乎没有什么区别,但是从一个软件工程师的角度来看,区别是非常巨大的。因为第一种需要业务系统开发相应的加解密功能,需要离线交换密钥,而第二种没有开发量。HTTPS是目前最流行的安全HTTP形式,由NetScape开创。在HTTPS中,URL以https://开头,而不是http://。使用HTTPS时,所有HTTP请求和响应在发送到网络之前都经过加密,这是在SSL层实现的。2、加密方式通过SSL层对明文数据进行加密,然后在Internet上传输,解决了HTTP协议原有的数据安全问题。一般来说,数据加密方法分为对称加密和非对称加密。2.1对称加密对称加密是指使用相同的密钥进行加密和解密。常见的算法有DES和AES,算法时间与密钥长度有关。对称密钥最大的缺点是需要离线维护和交换大量的对称密钥。加入具有n个实体的网络需要n(n-1)个密钥。2.2非对称加密非对称加密是指基于公钥/私钥的加密方法。常用算法为RSA,加密速度一般比对称加密慢。对称加密比非对称加密多了一步,即获取服务器的公钥,而不是各自维护的密钥。整个加密算法是基于一定的数论,达到的效果是加密结果不可逆。即用公钥加密的密文只能用私钥解密。在这种算法下,整个网络的密钥数量大大减少,每个人只需要维护一对公司密钥。也就是说,在一个有n个实体的网络中,key的数量是2n。它的缺点是运行缓慢。2.3混合加密在周星驰的电影《食神》中有一个场景,黑社会为了尿虾和牛丸的底盘划分而打架争论。食神道:“真麻烦,混在一起做牛尿丸子,笨蛋!”对称加密的优点是速度快,缺点是需要交换密钥。非对称加密的优点是不需要互钥,缺点是速度慢。只需将它们混合在一起即可使用。混合加密正是HTTPS协议使用的加密方式。先通过非对称加密交换对称密钥,再通过对称密钥传输数据。由于数据传输量远大于连接建立初期交换密钥时使用的非对称加密数据量,因此非对称加密带来的性能影响基本可以忽略,同时提高了效率。3、HTTPS握手可以看出,HTTPS在原有HTTP协议的基础上,增加了安全层处理:客户端与服务器端交换证书,验证身份。在现实中,服务器很少会通过验证客户端的证书来协商加密协议的版本和算法,这里可能存在版本不匹配导致无法协商对称密钥。这个过程使用非对称加密,使用3中的密钥和2中的加密算法对HTTP发送的明文进行加密,得到密文。感悟4.HttpClient对HTTPS协议的支持4.1获取SSL连接工厂和域名验证器作为软件工程师,我们关心的是“HTTPS协议”在代码中是如何实现的?探秘HttpClient源码,一切从HttpClientBuilder开始。publicCloseableHttpClientbuild(){//省略部分代码HttpClientConnectionManagerconnManagerCopy=this.connManager;//如果指定了连接池管理器,则使用指定的,否则新建默认if(connManagerCopy==null){LayeredConnectionSocketFactorysslSocketFactoryCopy=this.sslSocketFactory;if(sslSocketFactoryCopy==null){//如果启用环境变量,https版本和密码控制读finalString[]supportedProtocols=systemProperties?split(System.getProperty("https.protocols")):null;finalString[]supportedCipherSuites=systemProperties?split(System.getProperty("https.cipherSuites")):null;//如果不指定,则使用默认的域名验证器,验证域名是否与服务器返回的证书相关thesslsessionMatchHostnameVerifierhostnameVerifierCopy=this.hostnameVerifier;if(hostnameVerifierCopy==null){hostnameVerifierCopy=newDefaultHostnameVerifier(publicSuffixMatcherCopy);}//如果SslContext是specified,然后生成自定义SSL连接工厂,否则使用默认连接工厂Copy=newSSLConnectionSocketFactory((SSLSocketFactory)SSLSocketFactory.getDefault(),supportedProtocols,supportedCipherSuites,hostnameVerifierCopy);}else{sslSocketFactoryCopy=newSSLConnectionSocketFactory(SSLContexts.createDefault(),hostnameVerifierCopy);}}}//将ssl连接工厂注册到连接池在服务端,当需要产生Https连接时,会根据上面的SSL连接工厂@SuppressWarnings("resource")finalPoolingHttpClientConnectionManagerpoolingmgr=newPoolingHttpClientConnectionManager(RegistryBuilder.create().register("http",PlainConnectionSocketFactory.getSocketFactory()).register("https",sslSocketFactoryCopy).build(),null,null,dnsResolver,connTimeToLive,connTimeToLiveTimeUnit!=null?connTimeToLiveTimeUnit:TimeUnit.MILLISECONDS);//省略部分代码}上面的代码将一个Ssl连接工厂SSLConnectionSocketFactory创建并注册到connectipoolmanager上供以后使用连接池产生ssl连接的问题参考:http://www.cnblogs.com/kingszelda/p/8988505.html这个是配置SSLConnectionSocketFactory时用到的几个关键组件,域名验证器HostnameVerifier和上下文SSLContext。其中,HostnameVerifier用于验证服务器证书是否与域名匹配。有多种实现。DefaultHostnameVerifier采用默认的验证规则,替代了之前版本中的BrowserCompatHostnameVerifier和StrictHostnameVerifier。NoopH??ostnameVerifier替代AllowAllHostnameVerifier,采用不验证域名的策略。注意这里有一些区别,BrowserCompatHostnameVerifier可以匹配多级子域名,“*.foo.com”可以匹配“a.b.foo.com”。StrictHostnameVerifier不能匹配多级子域,只能匹配“a.foo.com”。4.4之后,HttpClient使用新的DefaultHostnameVerifier来替代以上两种策略,只保留了strict策略和StrictHostnameVerifier。因为严格的策略是IE6和JDK本身的策略,非严格的策略是curl和firefox的策略。即默认的HttpClient实现不支持多级子域名匹配策略。SSLContext中存放的是key相关的key信息,直接关系到业务,非常重要。这个后面会单独分析。4.2如何获取SSL连接如何从连接池中获取连接,这个过程在之前的文章中已经分析过,这里不再分析,连接参考:http://www.cnblogs.com/kingszelda/p/8988505.html。从连接池中获取连接后,如果连接不处于建立状态,则需要先建立连接。DefaultHttpClientConnectionOperator部分的代码为:publicvoidconnect(finalManagedHttpClientConnectionconn,finalHttpHosthost,finalInetSocketAddresslocalAddress,finalintconnectTimeout,finalSocketConfigsocketConfig,finalHttpContextcontext)throwsIOException{//之前在HttpClientBuilder中register了http与https不同的连接池实现,这里lookup获得Https的实现,即SSLConnectionSocketFactoryfinalLookupregistry=getSocketFactoryRegistry(context);finalConnectionSocketFactorysf=registry.lookup(host.getSchemeName());if(sf==null){thrownewUnsupportedSchemeException(host.getSchemeName()+"protocolisnotsupported");}//如果是在形式的ip地址可以直接使用,否则使用dns解析器解析对应的ipfinalInetAddress[]addresses=host.getAddress()!=null?newInetAddress[]{host.getAddress()}:this.dnsResolver.resolve(host.getHostName());finalintport=this.schemePortResolver.resolve(host);//一个域名可能对应多个IP,尝试依次连接for(inti=0;i0){sock.setReceiveBufferSize(socketConfig.getRcvBufSize());}if(socketConfig.getSndBufSize()>0){sock.setSendBufferSize(socketConfig.getSndBufSize());}finalintlinger=socketConfig.getSoLinger();if(linger>=0){sock.setSoLinger(真,流连忘返);}康恩。bind(sock);finalInetSocketAddressremoteAddress=newInetSocketAddress(address,port);if(this.log.isDebugEnabled()){this.log.debug("Connectingto"+remoteAddress);}try{//通过SSLConnectionSocketFactory连接绑定到connsock=sf.connectSocket(connectTimeout,sock,host,remoteAddress,localAddress,context);conn.bind(sock);if(this.log.isDebugEnabled()){this.log.debug("Connectionestablished"+conn);}返回;}//省略部分代码}}在上面的代码中,我们看到了建立SSL连接前的准备工作,这是一个普通的过程,普通的HTTP连接也是如此SSL连接的特殊过程在哪里?SSLConnectionSocketFactory部分分享代码如下:@OverridepublicSocketconnectSocket(finalintconnectTimeout,finalSocketsocket,finalHttpHosthost,finalInetSocketAddressremoteAddress,finalInetSocketAddresslocalAddress,finalHttpContextcontext)throwsIOException{Args.notNull(host,"HTTPhost");Args.notNull(remoteSocketnulletsocket=,"RemoteAddress=,"RemoteAddress)?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("Connectingsocketto"+remoteAddress+"withtimeout"+connectTimeout);}//建立连接sock.connect(remoteAddress,connectTimeout);}catch(finalIOExceptionex){try{sock.close();}catch(finalIOExceptionignore){}throwex;}//如果当前SslSocket是SSL握手和域名校验if(sockinstanceofSSLSocket){finalSSLSocketsslsock=(SSLSocket)sock;this.log.debug("Startinghandshake");sslsock.startHandshake();verifyHostname(sslsock,host.getHostName());returnsock;}else{//如果不是SslSocket,包装成SslSocketreturncreateLayeredSocket(sock,host.getHostName(),remoteAddress.getPort(),context);}}@OverridepublicSocketcreateLayeredSocket(finalSocketsocket,finalStringtarget,finalintport,finalHttpContextcontext)throwsIOException{//将一个普通的socket打包成SslSocket,socketfactory是根据HttpClientBuilder中的SSLContext生成的,里面包含关键信息finalSSLSocketsslsock=(SSLSocketftorySocket)this。(socket,target,port,true);//如果指定了SSL层协议版本和加密算法,则使用指定的,否则使用默认的if(supportedProtocols!=null){sslsock.setEnabledProtocols(supportedProtocols);}else{//如果未明确设置支持的协议,则删除所有SSL协议版本){enabledProtocols.add(协议);}}if(!enabledProtocolstocols.isEmpty()){sslsock.setEnabledProtocols(enabledProtocols.toArray(newString[enabledProtocols.size()]));}}if(supportedCipherSuites!=null){sslsock.setEnabledCipherSuites(supportedCipherSuites);}if(this.log.isDebugEnabled()){this.log.debug("Enabledprotocols:"+Arrays.asList(sslsock.getEnabledProtocols()));this.log.debug("Enabledciphersuites:"+Arrays.asList(sslsock.getEnabledCipherSuites()));}prepareSocket(sslsock);this.log.debug("Startinghandshake");//ssl连接握手sslsock.startHandshake();//握手成功后,验证返回的证书是否与域名一致verifyHostname(sslsock,目标);returnsslsock;}可见,对于一次SSL通信,首先建立一个普通的socket连接,然后进行一次SSL握手,然后验证证书与域名的一致性。后续操作就是通过SSLSocketImpl进行通信。协议细节体现在SSLSocketImpl类中,但这部分代码jdk并未开源。有兴趣的可以下载对应的openJdk源码继续分析。5.本文得出结论,HTTPS协议是HTTP的安全版本,在传输层实现了数据安全,但是在服务器上消耗了额外的CPU。HTTPS协议在协商密钥时采用非对称加密,密钥协商完成后采用对称加密。在某些场景下,即使使用https进行加解密,业务系统也会对消息进行二次加密和签名socket建立原则是先建立,再验证域名和证书的一致性。ssl层的加解密由jdk自己完成,不需要httpClient额外操作。