HTTPS IP直连问题小结

HTTPS IP直连问题小结背景任何一个移动 APP 项目都离不开网络接入功能 提升网络接入的质量几乎是所有移动项目的需求 很多项目都会引入 HTTPDNS 作为网络接入最基础也是最重要的优化之一 HTTPDNS 的核心是后台下发某个域名对应的最优 IP 基本点的可做到就近接入 即下发该域名终端就近地同运营商的 IP 好一点的则根据线上用户实际测速数据下发最优的 IP 而终端只需在 HTTP 接入时 将 URL 中的 HOST 从域名直接替换为后台下

背景

  • 省去DNS解析这一步,减少耗时
  • 就近接入甚至就快接入,减少耗时
  • 避免DNS劫持
  • 当终端有多个IP接入选择时,有一定容灾能力

HTTPS IP直连问题与解决

问题1. 证书HOST校验问题

终端在SSL握手过程会校验当前请求URL的HOST是否在服务端证书的可选域名列表里。举个例子,假设原本想要请求的URL为https://v.html5..com,而使用IP直连后实际请求的URL为:https://183.61.38.230:443。此时服务端返回的证书可选域名列表如下
这里写图片描述
由于请求的HOST被替换成了IP,导致底层在进行证书的HOST校验时失败,最终请求失败。

问题2. SNI问题

解决了问题1后,请求可以成功是“纯属意外”。事实上,183.61.38.230:443这个转发层部署了多个域名的证书,除了问题1中的v.html5..com,还有https://ag..com等域名。此时如果用https://ag..com进行IP直连,请求会失败,因为当终端使用IP直连时,服务端SSL握手阶段获取到的域名为调度后的IP,服务端无法找到匹配的证书,只能返回默认的证书或者不返回。喵喵问题1中的图,默认返回的证书的拓展域名列表是不包含ag..com的,所以证书的HOST校验还是会失败,导致请求失败。

这个问题的解决也并不复杂:

  1. 系统提供接口,允许终端传入自定义SSLSocketFactory。SSLSocketFactory是创建SSLSocket的工厂,SSLSocket是Socket拓展,有SSL握手功能。
  2. 系统提供解决SNI问题的实现类SSLCertificateSocketFactory

SSLSocketFactory接口有很多个创建Socket的方法,但是底层回调的就是Socket createSocket(Socket s, String host, int port, boolean autoClose),后文也会提到。

进行了这一步操作后,对https://ag..com进行IP直连也可以成功了。因为SSL握手时,会在SNI拓展字段中传入实际请求域名:
这里写图片描述
而服务端也能返回正确的非默认的证书了:
这里写图片描述


问题3. 连接复用问题

解决了证书HOST校验问题与SNI问题后,请求确实可以成功了,但是却不知不觉中引入了一个巨大的性能问题,即连接复用失效的问题。

在HTTP1.0时代,每一个请求都必须经过三步,即建立连接,请求响应数据,然后就断开连接了,下一次请求又重新建立连接。如果是HTTPS就更加糟糕,建立连接后还要多一个更加耗时耗资源的SSL握手过程。到了HTTP1.1时代,引入了连接复用,当CS/BS都支持连接复用时,握手后,会在同一个连接上不断收发数据,直到一方断开。

如果服务端支持连接复用,即响应头不带有Connection:close时,HttpURLConnection使用域名接入时,连接复用运作正常:
这里写图片描述
而使用IP直连时,连接复用却失效:
这里写图片描述
这里写图片描述



不解决这个问题,单从耗时来说,使用IP直连还不如直接使用域名,因为每次握手的耗时是非常明显的,所以需要研究下底层是怎么去进行连接复用的。

因为Android#HttpURLConnection从4.4开始就将底层实现切换到了OkHttp,所以可以直指矛头,直接分析OkHttp的源码(当时我找的是最新版本的OkHttp代码,因为感觉连接复用这一块是比较基础的代码应该不会有较大变动,其实每一个Android版本对应的OkHttp版本都不同)。

沿着请求接口Debug追溯可知:

  1. 当一个请求连接建立完成后,会将这个连接缓存到连接池中;
  2. 一个新的请求过来时,会先判断连接池中是否已有可复用连接,有则复用,无则新建连接。

连接复用底层要求,两个请求的HostnameVerifier对象要么是同一个对象,要么是两个相等的对象。为了防止同一个对象的内部数据结构有变化产生影响,采取重写equals方法使条件成立。SSLSocketFatcory同理。

public class MyHostnameVerifier implements HostnameVerifier{ 
     public String bizHost; public MyHostnameVerifier(String bizHost) { this.bizHost = bizHost; } @Override public boolean verify(String hostname, SSLSession session) { return HttpsURLConnection.getDefaultHostnameVerifier().verify(bizHost, session);; } @Override public boolean equals(Object o) { if (TextUtils.isEmpty(bizHost) || !(o instanceof MyHostnameVerifier)) { return false; } String thatHost = ((MyHostnameVerifier) o).bizHost; if (TextUtils.isEmpty(thatHost)) { return false; } return bizHost.equals(thatHost); } }

两个请求传入相等的HostnameVerifier对象与SSLSocketFatcory对象后,HTTPS IP直连连接复用效果与使用域名接入一致了。

问题4. 兼容性问题

解决了上述三个问题,HTTPS使用IP直连应该是初见成效了,由于给底层设置了两个自定义的对象,而不同系统版本底层网络这块是有较大变化的,所以有必要进行一下兼容性测试。不测不知道,一测又吓一跳了。

当API小于23时,之前进行的SNI测试,即用https://ag..com进行IP直连请求,又无法成功。但是使用阿里云的HTTPDNS+SNI参考实现,仍然成功。其实SNI问题的解决当时就是参考的阿里的方案,就是问题2中设置自定义SSLSocketFactory,不过他们在代理SSLCertificateSocketFactory时,调用的是这个接口(后文称接口1):
这里写图片描述
而本文的实现则是调用:Socket createSocket(Socket s, String host, int port, boolean autoClose)这个接口(后文称接口2)。

OkHttp层ConnectInterceptor有固定操作:

  1. 创建一个普通rawSocket
  2. 回调SDK层创建一个SSLSocket
    sslSocket = (SSLSocket) sslSocketFactory.createSocket(
    rawSocket, address.url().host(), address.url().port(), true);

接口2比接口1多调用一个verifyHostname方法:
这里写图片描述
这里写图片描述

verifyHostname方法里是会进行握手和证书HOST校验的
这里写图片描述

当SSLCertificateSocketFactory调用接口2时,会在此类进行握手,并且此时并没有为SSLScoket设置域名,所以是不支持SNI的,证书HOST校验又一次失败…而API23以后,OpenSSL层的实现有变化,使得在SSLCertificateSocketFactory层的握手也支持SNI,HOST校验成功。

当SSLCertificateSocketFactory调用接口1时,SSL握手会在SDK层进行,握手前会为SSLSocket设置域名,所以可以支持SNI。

问题5. Session复用问题

SSL握手过程中,密钥协商是其中最耗资源和时间的过程,Session复用能节省协商的消耗。Session复用同样需要CS双方支持。

Session复用有两种方式:

  1. 通过Session ID
    SSL握手过后,服务端可以将协商后的信息存起来,生成 Session ID ,终端也可以保存 Session ID,并在后续的 Client Hello 握手中带上它,如果服务端能找到与之匹配的信息,就可以快速完成握手。
  2. 通过Session Ticket
    Session ID机制有一些弊端,例如:1)集群多机之间往往没有同步 Session ID信息;2)服务端存储的Session ID会不断增加,消耗内存等资源
    Session Ticket 是用只有服务端知道的安全密钥加密过的会话信息,最终保存在终端端。终端如果在 Client Hello 时带上了 Session Ticket,只要服务端能成功解密,就可以完成快速握手。

自定义SSLSocketFatroy在之前实现的基础上,为SSLCertificateSocketFactory对象添加系统对象SSLSessionCache,即可实现Session Ticket复用会话。

  1. 握手完成后服务端返回Session Ticket
    这里写图片描述
  2. 连接断开,再次SSL握手时,Client Hello携带Session Ticket。此时服务端不会下发证书,省去协商过程。
    这里写图片描述
    这里写图片描述

本来到此整个过程就结束了,但是直觉告诉我,还是要做一下兼容性测试…发现当API>=23,HTTPS IP直连时,不管是否设置SSLSessionCache,服务端均不会返回Session Ticket与Session ID。而使用域名接入时,则正常返回Session Ticket。底层实现变化,尚未查明原因~

最终优化效果

这里写图片描述


参考


更新

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/233780.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • CountDownLatch踩过的坑[通俗易懂]

    CountDownLatch踩过的坑[通俗易懂]线上生产环境dubbo报线程池满了,经过一天排查锁定在开三个线程计算最后合并数据的步骤中。简单描述下该步骤线程开三个调用三个不同的方法使用countdownlatch计数器等待三个方法全部执行完成合并数据。但是由于其中一个方法调用第三方接口,接口返回异常导致转换数据报错。导致其中一个方法未正常完成。举例demo:publicstaticvoidmain(String[]a…

    2022年7月13日
    35
  • 【视频教程】JEECG 入门视频教程大全+历史版本号代码下载[通俗易懂]

    【视频教程】JEECG 入门视频教程大全+历史版本号代码下载

    2022年1月27日
    37
  • Java面试题大全带答案「建议收藏」

    Java面试题大全带答案「建议收藏」本人发现网上虽然有不少Java相关的面试题,但第一未必全,第二未必有答案,第三虽然有答案,但未必能在面试中说,所以在本文里,会不断收集各种面试题,并站在面试官的立场上,给出我自己的答案。第一部分、Java基础1.JDK和JRE有什么区别?JDK是java的开发工具包,有JDK8,9甚至到14的差别,安装以后,不仅包含了java的开发环境,比如java.exe,还包含了运行环境(jre)相关包。 JRE是java运行环境,一般装好JDK后,系统里会有对应的JRE环境。2..

    2022年6月21日
    31
  • 索尼a5100微单参数_索尼微单a5100拍摄教程

    索尼a5100微单参数_索尼微单a5100拍摄教程入门的第一款微单—SONYa51002018-11-1109:33:0012点赞20收藏33评论开篇:这一刻我想将你永恒定格上帝给我我们一双黑色的眼睛,让我们用它来寻找光明。在寻找光明的途中我们发现光和影这一双神奇的上帝之手,让我们生活的环境变得那么的美好。有无数个美好的瞬间在我们眼前,多么希望将这个美好定格,留下这美丽的瞬间。正文:咔嚓之间将你定格——入门的第一款相机说到相机可谓是百家争…

    2025年6月7日
    0
  • php navigator,navigator对象

    php navigator,navigator对象navigator对象appName:浏览器软件名称,主要用来判断客户使用的是什么核心的浏览器。如果是IE浏览器的话,返回值为:MicrosoftInternetExplorer如果是Firefox浏览器的话,返回值为:NetscapeappVersion:浏览器软件的核心版本号。systemLanguage:系统语言userLanguage:用户语言platform:平台HTML>ph…

    2022年9月12日
    0
  • 本我、自我与超我_自我本我超我论文

    本我、自我与超我_自我本我超我论文本我、自我与超我

    2025年6月14日
    0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号