HTTP常见面试题

HTTP是什么

HTTP是超文本传输协议也就是HyperText Transfer Protocol

HTTP的 超文本传输协议,可以拆成三个部分

  • 超文本
  • 传输
  • 协议

超文本,超越了普通文字本体

HTTP是一个在计算机世界里面专门在俩点之间传输文字,图片,音频,视频等,超文本数据的约定和规范

常见字段:

Host字段

客户端发送请求时用来指定服务器的域名,有了 Host 字段,就可以将请求发往「同一台」服务器上的不同网站。

Content-Length

服务器在返回数据时,会有content-Length字段,表明本次回应的数据长度

Content-Length:1000

如上面是告诉浏览器,本地服务器返回数据的长度是1000个字节,后面的字节就属于下一个回应了.

大家都知道HTTP是基于TCP传输协议进行通信的,而使用了TCP传输协议就会存在一个黏包的问题HTTP协议通过设置回车符,换行符作为HTTP header的边界,通过Content-Length字段作为HTTP body的边界,这俩个方式都是为了解决黏包问题.

Connection

connection字段最长由于客户端要求服务器使用HTTP长链接机制,一遍其他请求复用

HTTP长链接的特点是,只有任意一端没有明确提出断开需求,则保持TCP连接状态

HTTP/1.1 版本的默认连接都是长连接,但为了兼容老版本的 HTTP,需要指定 Connection 首部字段的值为 Keep-Alive

Connection: Keep-Alive

开启了HTTP Keep-Alive机制后,连接就不会中断,而是保持连接.当客户端发送另一个请求时,它会使用同一个连接,一直持续到客户端或服务器端提出断开连接

PS:大家不要把 HTTP Keep-Alive 和 TCP Keepalive 搞混了,这两个虽然长的像,但是不是一个东西

content-Type

content-Type字段用于服务器回应时,告诉客户端本次数据是什么格式

Content-Type: text/html; Charset=utf-8

上面的类型表明,发送的是网页,而且编码是UTF-8。

客户端请求的时候,可以使用 Accept 字段声明自己可以接受哪些数据格式。

Accept: */*

上面代码中,客户端声明自己可以接受任何格式的数据

Content-Encoding

Content-Encoding 字段说明数据的压缩方法。表示服务器返回的数据使用了什么压缩格式

Content-Encoding: gzip

上面表示服务器返回的数据采用了 gzip 方式压缩,告知客户端需要用此方式解压。

客户端在请求时,用 Accept-Encoding 字段说明自己可以接受哪些压缩方法。

Accept-Encoding: gzip, deflate

GET 请求可以带 body 吗?

RFC 规范并没有规定 GET 请求不能带 body 的。理论上,任何请求都可以带 body 的。只是因为 RFC 规范定义的 GET 请求是获取资源,所以根据这个语义不需要用到 body。

另外,URL 中的查询参数也不是 GET 所独有的,POST 请求的 URL 中也可以有参数的。

HTTP缓存技术

对于一些重复性的HTTP请求,比如每次得到的数据都一样的,我们可以把这对[请求-响应]的数据都缓存在本地,那么下次就直接读取本地的数据,不比通过网络获取服务器的响应了这样的话HTTP/1.1的性能肯定能有肉眼可见的提升

所以避免发送HTTP请求的方法就是通过缓存技术,HTTP设计者在之前就考虑到了这点,因此HTTP协议的头部不少是针对缓存的字段

HTTP缓存有俩种实现方式,分别是强制缓存和协商缓存

强制缓存

强制缓存只要浏览器判断没有过期,啧直接使用浏览器本地的缓存,决定是否缓存的主动性在于浏览器这边

如下图返回的是200状态码,但size项表示的是from disk cache,就是使用了强制缓存

强制缓存是利用下面这俩个HTTP响应头部(Response Header) 字段实现的,它们都表示自愿在客户端的有效期

  • Cache-Control,是一个相对时间
  • Expires是一个绝对时间

如果同时有Cache-Control和Expires字段的话,Cache-Control的优先级高于Expires

Cache-control选项更多一些,设置更加精细,所以建议使用Cache-Control来实现强制缓存.具体的实现流程如下

  1. 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同事,在response头部加上Cache-Control,Cache-Control中设置了过期时间大小
  2. 浏览器再次请求访问服务器中的该资源时,回显通过请求资源的时间与Cache-Control中设置的过期时间大小,来计算出该浏览器是否过期,如果没有,则使用该缓存,否则重新请求服务器
  3. 服务器再次受到请求后,会再次更新Response头部的Cache-Control

协商缓存

我们在浏览器使用开发者工具时,你可能会看到过某些请求的响应码是304,这个是告诉浏览器可以使用本地缓存的资源,通过这种服务端告知客户端是否可以使用缓存的方式被称为协商缓存

上图就是一个协商缓存的过程,所以协商缓存就是与服务端协商之后,通过协商结果来判断是否使用本地缓存

协商缓存可以基于两种头部来实现。

第一种:请求头部中的 If-Modified-Since 字段与响应头部中的 Last-Modified 字段实现,这两个字段的意思是:

  • 响应头部中的 Last-Modified:标示这个响应资源的最后修改时间;
  • 请求头部中的 If-Modified-Since:当资源过期了,发现响应头中具有 Last-Modified 声明,则再次发起请求的时候带上 Last-Modified 的时间,服务器收到请求后发现有 If-Modified-Since 则与被请求资源的最后修改时间进行对比(Last-Modified),如果最后修改时间较新(大),说明资源又被改过,则返回最新资源,HTTP 200 OK;如果最后修改时间较旧(小),说明资源无新修改,响应 HTTP 304 走缓存。

第二种:请求头部中的 If-None-Match 字段与响应头部中的 ETag 字段,这两个字段的意思是:

  • 响应头部中 Etag:唯一标识响应资源;
  • 请求头部中的 If-None-Match:当资源过期时,浏览器发现响应头里有 Etag,则再次向服务器发起请求时,会将请求头 If-None-Match 值设置为 Etag 的值。服务器收到请求后进行比对,如果资源没有变化返回 304,如果资源变化了返回 200。

第一种实现方式是基于时间实现的,第二种实现方式是基于一个唯一标识实现的,相对来说后者可以更加准确地判断文件内容是否被修改,避免由于时间篡改导致的不可靠问题。

如果在第一次请求资源的时候,服务端返回的 HTTP 响应头部同时有 Etag 和 Last-Modified 字段,那么客户端再下一次请求的时候,如果带上了 ETag 和 Last-Modified 字段信息给服务端,这时 Etag 的优先级更高,也就是服务端先会判断 Etag 是否变化了,如果 Etag 有变化就不用在判断 Last-Modified 了,如果 Etag 没有变化,然后再看 Last-Modified。

为什么 ETag 的优先级更高?这是因为 ETag 主要能解决 Last-Modified 几个比较难以解决的问题:

  1. 在没有修改文件内容情况下文件的最后修改时间可能也会改变,这会导致客户端认为这文件被改动了,从而重新请求;
  2. 可能有些文件是在秒级以内修改的,If-Modified-Since 能检查到的粒度是秒级的,使用 Etag就能够保证这种需求下客户端在 1 秒内能刷新多次;
  3. 有些服务器不能精确获取文件的最后修改时间。

注意,协商缓存这两个字段都需要配合强制缓存中 Cache-Control 字段来使用,只有在未能命中强制缓存的时候,才能发起带有协商缓存字段的请求

当使用 ETag 字段实现的协商缓存的过程:

  • 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在 Response 头部加上 ETag 唯一标识,这个唯一标识的值是根据当前请求的资源生成的;
  • 当浏览器再次请求访问服务器中的该资源时,首先会先检查强制缓存是否过期:

    • 如果没有过期,则直接使用本地缓存;
    • 如果缓存过期了,会在 Request 头部加上 If-None-Match 字段,该字段的值就是 ETag 唯一标识;
  • 服务器再次收到请求后,

会根据请求中的 If-None-Match 值与当前请求的资源生成的唯一标识进行比较

    • 如果值相等,则返回 304 Not Modified,不会返回资源
    • 如果不相等,则返回 200 状态码和返回资源,并在 Response 头部加上新的 ETag 唯一标识;
    • 如果浏览器收到 304 的请求响应状态码,则会从本地缓存中加载资源,否则更新资源。

    HTTP/1.1特性

    优点

    简单,灵活,易于扩展,应用广泛和跨平台

    1. 简单

    HTTP 基本的报文格式就是 header + body,头部信息也是 key-value 简单文本的形式,易于理解,降低了学习和使用的门槛。

    2. 灵活和易于扩展

    HTTP 协议里的各类请求方法、URI/URL、状态码、头字段等每个组成要求都没有被固定死,都允许开发人员自定义和扩充

    同时 HTTP 由于是工作在应用层( OSI 第七层),则它下层可以随意变化,比如:

    • HTTPS 就是在 HTTP 与 TCP 层之间增加了 SSL/TLS 安全传输层;
    • HTTP/1.1 和 HTTP/2.0 传输协议使用的是 TCP 协议,而到了 HTTP/3.0 传输协议改用了 UDP 协议。

    3. 应用广泛和跨平台

    互联网发展至今,HTTP 的应用范围非常的广泛,从台式机的浏览器到手机上的各种 APP,从看新闻、刷贴吧到购物、理财、吃鸡,HTTP 的应用遍地开花,同时天然具有跨平台的优越性。

    缺点

    HTTP协议里有优缺点一体的双刃剑,分别是[无状态,明文传输],同时还有一大缺点[不安全]

    无状态双刃剑

    无状态的好处,因为服务器不会去记忆HTTP的状态.所以不需要额外资源来记录状态信息,这样能建琴服务器的负担,能够把更多的CPU和内存用来对外提供服务

    无状态的坏处,既然服务器没有记忆能力,它在完成有关联性操作时会非常麻烦.

    比如 登录->添加购物车->下单->结算->支付,这系列操作都要知道用户的身份才行,但服务器不知道这些请求是有关联的,每次都要访问一遍身份信息

    对于无状态的问题,解决方案又很多种,比较简单的方式用Cookie技术

    Cookie通过在请求和响应报文中写入Cookie信息来控制客户端的状态

    相当于,在客户端第一次请求后,服务器会下发一个装有客户信息的小贴纸,后续请求服务器的时候,带上小贴纸,服务器就能认得了

    明文传输双刃剑

    明文传输过程中的信息是可方便阅读的,比如wireshark抓包都可以直接肉眼查看,为我们调试带来了极大的便利性.

    但正是这样,HTTP的所有信息都暴露在了光天化日之下,相当于信息裸奔.在漫长的传输过程中,信息的内容都毫无隐私可言,很容易被窃取

    HTTP 的安全问题,可以用 HTTPS 的方式解决,也就是通过引入 SSL/TLS 层,使得在安全上达到了极致。

    性能

    长链接

    HTTP协议是基于TCP/IP,并且使用了[请求-应答]的通信模式,所以性能的关键就在这俩点里

    早期 HTTP/1.0 性能上的一个很大的问题,那就是每发起一个请求,都要新建一次 TCP 连接(三次握手),而且是串行请求,做了无谓的 TCP 连接建立和断开,增加了通信开销。

    为了解决上述 TCP 连接问题,HTTP/1.1 提出了长连接的通信方式,也叫持久连接。这种方式的好处在于减少了 TCP 连接的重复建立和断开所造成的额外开销,减轻了服务器端的负载。

    持久连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。

    当然,如果某个 HTTP 长连接超过一定时间没有任何数据交互,服务端就会主动断开这个连接。

    管道网络传输

    HTTP/1.1采用了长链接的方式,这使得管道(pipline)网络传输成为了可能

    即在同一个TCP连接里面,客户可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体响应时间

    举例来说,客户端需要请求两个资源。以前的做法是,在同一个 TCP 连接里面,先发送 A 请求,然后等待服务器做出回应,收到后再发出 B 请求。那么,管道机制则是允许浏览器同时发出 A 请求和 B 请求,如下图:

    但是服务器必须按照接受请求的顺序发送对这些管道请求的响应

    如果服务端在处理A请求时耗时比较长,那么在后续请求的处理都会被阻塞住,这称为队头堵塞.

    所以HTTP/1.1管道解决了请求队头阻塞,但是没解决响应的队头阻塞

    TIP

    注意!!!

    实际上 HTTP/1.1 管道化技术不是默认开启,而且浏览器基本都没有支持,所以后面所有文章讨论 HTTP/1.1 都是建立在没有使用管道化的前提。大家知道有这个功能,但是没有被使用就行了。

    队头阻塞

    请求-应答的模式会造成HTTP性能的问题.为什么呢?

    因为当顺序发送的请求序列中的一个请求因为某种原因被阻塞时,在后面排队的所有请求也一同被阻塞了,会招致客户端一直请求不到数据,这也就是「队头阻塞」,好比上班的路上塞车。

    总之 HTTP/1.1 的性能一般般,后续的 HTTP/2 和 HTTP/3 就是在优化 HTTP 的性能。

    HTTP与HTTPS

    HTTP和HTTPS有哪些区别

    • HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。
    • HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
    • 两者的默认端口不一样,HTTP 默认端口号是 80,HTTPS 默认端口号是 443。
    • HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。

    解决问题

    HTTP由于是明文传输,存在以下三个风险

    • 窃听风险,比如通信链路上可以获取通信内容
    • 篡改风险:比如强制植入垃圾广告
    • 冒充风险,比如冒充淘宝网站

    HTTPS 在 HTTP 与 TCP 层之间加入了 SSL/TLS 协议,可以很好的解决了上述的风险:

    • 信息加密:交互信息无法被窃取
    • 校验机制:无法串改通讯内容
    • 身份证书:证明xx网站是真的

    可见,只要自身不作恶 ,SSL/TLS协议是能保证通信安全的

    HTTPS是如何解决上面三个风险的?

    • 混合加密的方法实现信息的机密性,解决了窃听的风险
    • 摘要算法的方式来实现完整性,它能够为数据生成独一无二的质问,用于校验数据的完整性,解决了篡改的风险
    • 将服务器公钥放到数字证书中,解决了冒充的风险

    混合加密

    通过混合加密的方式可以实现信息的机密性,解决了窃听的风险

    HTTP采用的是对称加密和非对称加密结合点的混合加密方式

    • 在通信建立前采用非对称加密的方式交换会话秘钥,后续不再使用户非对称加密
    • 在通信过程中全部使用对称加密的会话秘钥的方式加密数据

    采用混合加密的方式原因

    对称加密:使用一个秘钥,运算速度快,秘钥必须保密,无法做到安全的秘钥交换

    非对称加密使用俩个秘钥:公钥和私钥,公钥和私钥可以任意分发和私钥保密,解决了秘钥交换问题但速度慢

    摘要算法+数字签名

    为了保证传输的内容不被修改,我们需要对内容计算一个指纹,然后同内容一同传输给对方

    对方收到后,也是对内容计算出一个质问,然后跟发送方发出的指纹做一个比较,如果指纹相同,说明内容没有被篡改,否则就可以判断出内容被篡改了

    那么,在计算机里会用摘要算法(哈希函数)来计算出内容的哈希值,也就是内容的指纹,这个哈希值是唯一的,切无法通过哈希值推导出内容

    通过哈希算法可以确保内容不会被篡改,但是并不能保证内容+哈希值不会被中间人替换,因为这里缺少客户端中收到的消息是否来源服务器的证明.

    举个例子,你想向老师请假,一般来说是要求家长写一份请假理由并签名老师才允许你请假.

    但你又模仿你爸爸字迹的能力,你用爸爸的自己写了一份请假理由然后签上你爸爸的名字,老师一看到这个假条,查看字迹和签名,就误以为是你爸爸写的,就会允许你请假

    那么作为老师,,要如何避免这种情况发生呢?现实生活中可以通过电话或视频的方式来确认是否是由父母发出的请假,但是作为计算机里没有这种操作

    为了避免这种情况,计算机会用非对称加密来解决,共有俩个秘钥

    • 一个是公钥,这个是可以公开给所有人的
    • 一个是私钥,这个必须由本人管理,不可泄露

    这俩个秘钥可以双向加密的,比如可以用公钥加密内容,然后用私钥解密,也可以私钥加密内容,公钥解密内容

    • 公钥加密,私钥解密.这个目的是为了保证内容传输的安全,因为被公钥加密的内容,其他人是无法解密的,只有持有私钥的人,才能解密出实际的内容,其他人是无法解密的,只有持有私钥的人,才能解密出实际内容
    • 私钥加密,公钥解密.这个目的是为了保证消息不会被冒充,因为私钥是不可泄露的,如果公钥能正常解密出私钥加密的内容,就能证这个消息是来源持有私钥身份的人发送的

    一般我们不会用非对称加密来加密实际传输内容,因为非对称加密的计算比较耗费性能

    所以非对称加密的用途主要通过[私钥加密 公钥解密的方式]来确定消息的身份,也就是我们常说的数字签名算法,就是用这种不同的方式,不过私钥加密的内容不是内容本身,而是对内容的哈希值加密

    私钥是由服务器保管,然后会向客户端颁发对应的公钥.如果客户端收到的信息,能被公钥解密,就说明该消息是由服务器发送的.

    引入了数字签名算法后,就无法模仿你爸爸的字迹来进行签名了,你爸爸 手上有私钥,你老师有公钥

    这样只有用你爸爸手上的私钥才能对请假条进行签名,老师通过公钥看能不能解出这个签名,如果能解出并确认内容的完整性,就是你爸爸发起的请假条,这样老师才允许你请假,否则就不认

    数字证书

    前面我们知道

    • 可以通过哈希算法来保证消息的完整性
    • 可以通过数字签名来哦保证消息的来源可靠性(能确认消息是由私钥的一方发送的)

    但是这还远远不够,还缺少身份验证的环节,万一公钥是被伪造的呢?

    还是拿请假的例子,虽然你爸爸持有私钥,老师通过是否能用公钥解密来确认这个请假条是不是来源你父亲的。

    但是我们还可以自己伪造出一对公私钥啊!

    你找了个夜晚,偷偷把老师桌面上和你爸爸配对的公钥,换成了你的公钥,那么下次你在请假的时候,你继续模仿你爸爸的字迹写了个请假条,然后用你的私钥做个了「数字签名」。

    但是老师并不知道自己的公钥被你替换过了,所以他还是按照往常一样用公钥解密,由于这个公钥和你的私钥是配对的,老师当然能用这个被替换的公钥解密出来,并且确认了内容的完整性,于是老师就会以为是你父亲写的请假条,又允许你请假了。

    好家伙,为了一个请假,真的是斗智斗勇。

    后面你的老师和父亲发现了你伪造公私钥的事情后,决定重新商量一个对策来应对你这个臭家伙。

    正所谓魔高一丈,道高一尺。

    既然伪造公钥私钥那么随意,所以你爸把他的公钥注册到警察局,警察局用他们的私钥对你父亲的公钥做了个数字签名,然后把你爸爸的[个人信息+公钥+数字签名]打包成一个数字证书,也就是说这个数字证书包含你爸爸的公钥.

    这样你爸爸如果因为家里确实有事要向老师帮你请假的时候,不仅不会用自己的私钥对内容进行签名,还会把数字证书给到老师

    老师拿了数字证书后,首先回去警察局验证这个数字证书是否合法,因为数字证书里有警察局的数字签名,警察局要验证证书合法性的时候,用自己的公钥解密,如果能解密成功,就说明这个数字证书是在警察局注册过的,就认为该数字证书是合法的.然后就会把证书里头的公钥(你爸爸的)给到老师

    由于警察局验证了数字证书是合法的,那么久能证明这个公钥是你父亲的,于是老师就可以安心的 用这个公钥解密出请假条,如果能解密,就证明是你爸爸写的请假条

    正式通过了一个权威机构来证明你爸爸的身份,所以你的伪造公私钥这个小伎俩就没用了

    在计算机里,这个权威机构就是CA(数字证书认证机构),将服务器公钥放在数字证书(由数字证书机构颁发)中,只要证书是可信的,公钥就是可信的

    通过数字证书的方式保证服务器公钥的身份,解决冒充风险

    HTTPS是如何建立连接的

    SSL/TLS协议基本流程

    • 客户端想服务器索要验证服务器的公钥
    • 双方协商产生会话秘钥
    • 双方采用会话秘钥进行通信加密

    前俩部也就是SSL/TLS的建立过程,也就是TLS握手阶段.

    TLS握手阶段设计四次通信,使用不同秘钥交换算法,TLS握手流程也会会不一样的,现在常用的秘钥交换算法有俩种RSA算法和ECDHE算法

    基于RSA算法的TLS握手过程比较容易理解,所以这里线用这个给大家展示TLS握手过程

    clientHello

    首先,客户端想服务器发起加密通讯请求,也就是ClientHello 请求

    在这一步,客户端主要想服务器端发送以下信息

    1. 客户端支持的TLS协议版本
    2. 客户端产生的随机数(Client Random),后面用于生成会话秘钥之一
    3. 客户端支持的密码套件列表,如RSA加密算法

    severHello

    服务器收到客户端请求后,想客户端发出响应,也就是ServerHello,服务器回应的内容有如下内容

    1. 确认TLS协议版本,如果浏览器不支持,啧关闭加密通信
    2. 服务器产生随机数ServerRandom,也是后面用于生产会话秘钥条件之一
    3. 确认密码套件列表,如RSA加密算法
    4. 服务器的数字证书

    客户端回应

    客户端收到服务器端的回应之后,首先通过浏览器或者操作系统中的CA公钥,确认服务器的数字证书真实性

    如果证书没有问题,客户端会从数字证书中取出服务器的公钥,然后使用它加密报文,向服务器发送如下信息

    1. 一个随机数(pre-master key).该随机数会被服务器公钥加密
    2. 加密通信算法改变通知,表示随后的信息豆浆用会话秘钥加密通信
    3. 客户端握手结束通知表示客户端的握手阶段已经结束.这一项同时把之前所有内容的发生的数据做个摘要,用来提供服务端校验

    上面第一项的随机数是整个握手阶段的第三个数,会发送给服务器端,所以这个随即客户端和服务端都是一样的

    服务器客户端有了这三个随机数(Client Random,Server Random,pre-master key)接着就用双方协商的加密算法,各自生成本次通信的会话秘钥

    服务器最后回应

    服务端收到客户端的第三个随机数(pre-master key)之后,通过协商的加密算法,计算出本次通信的会话秘钥

    然后,想客户端发送最后的信息:

    1. 加密通信算法改变通知,表示随后的信息都将用会话秘钥加密通信
    2. 服务器握手结束通知,表示服务器的握手阶段已经结束.这一项同时把之前所有内容的发生数据做个摘要,原来提供客户端校验

    至此,整个TLS握手阶段全部结束.接下来客户端与服务器进入加密通信,就完全是使用普通的HTTP协议,只不过用会话秘钥加密内容

    不过,基于 RSA 算法的 HTTPS 存在「前向安全」的问题:如果服务端的私钥泄漏了,过去被第三方截获的所有 TLS 通讯密文都会被破解。

    为了解决这个问题,后面就出现了 ECDHE 密钥协商算法,我们现在大多数网站使用的正是 ECDHE 密钥协商算法,关于 ECDHE 握手的过程可以看这篇文章:HTTPS ECDHE 握手解析

    客户端校验数字证书的流程是怎么样的?

    接下来是数字证签发和验证流程

    CA证书签发过程,如上图左边部分

    • 首先CA会把持有者公钥,用途,颁发者,有效时间等信息达成一个包,然后对这些信息进行hash计算,得到一个hash值
    • 然后CA会使用自己的私钥将该hash值加密,生成Certificate Signature,也就是CA对证书做了签名
    • 最后将Certicate Signature内容,得到一个hash值H2
    • 最后比较H1和H2,如果值相同则为可信赖证书,否则认为证书不可信

    但事实上,证书验证过程中还存在一个信任链的问题,因为我们向CA申请证书一般不是跟证书签发的,而是由中间证书签发的,比如百度的证书,从下图可以看到有三级

    对于这种三级层级关系的证书的验证过程如下:

    • 客户端收到 baidu.com 的证书后,发现这个证书的签发者不是根证书,就无法根据本地已有的根证书中的公钥去验证 baidu.com 证书是否可信。于是,客户端根据 baidu.com 证书中的签发者,找到该证书的颁发机构是 “GlobalSign Organization Validation CA - SHA256 - G2”,然后向 CA 请求该中间证书。
    • 请求到证书后发现 “GlobalSign Organization Validation CA - SHA256 - G2” 证书是由 “GlobalSign Root CA” 签发的,由于 “GlobalSign Root CA” 没有再上级签发机构,说明它是根证书,也就是自签证书。应用软件会检查此证书有否已预载于根证书清单上,如果有,则可以利用根证书中的公钥去验证 “GlobalSign Organization Validation CA - SHA256 - G2” 证书,如果发现验证通过,就认为该中间证书是可信的。
    • “GlobalSign Organization Validation CA - SHA256 - G2” 证书被信任后,可以使用 “GlobalSign Organization Validation CA - SHA256 - G2” 证书中的公钥去验证 baidu.com 证书的可信性,如果验证通过,就可以信任 baidu.com 证书。

    在这四个步骤中,最开始客户端只信任根证书 GlobalSign Root CA 证书的,然后 “GlobalSign Root CA” 证书信任 “GlobalSign Organization Validation CA - SHA256 - G2” 证书,而 “GlobalSign Organization Validation CA - SHA256 - G2” 证书又信任 baidu.com 证书,于是客户端也信任 baidu.com 证书。

    总括来说,由于用户信任 GlobalSign,所以由 GlobalSign 所担保的 baidu.com 可以被信任,另外由于用户信任操作系统或浏览器的软件商,所以由软件商预载了根证书的 GlobalSign 都可被信任。

    操作系统里一般都会内置一些根证书

    最后一个问题,为什么需要证书链这么麻烦的流程?Root CA 为什么不直接颁发证书,而是要搞那么多中间层级呢?

    这是为了确保根证书的绝对安全性,将根证书隔离地越严格越好,不然根证书如果失守了,那么整个信任链都会有问题。

    HTTPS的原因数据是如何保证完整性的

    TLS在是线上分为握手协议和记录协议俩层

    TLS握手协议就是我们前面所说的TLS四次握手的过程,负责协商加密算和对称秘钥,后续用此秘钥来保护应用程序数据(即HTTP数据)

    TLS记录协议负责保护应用数据并验证其完整性和来源,所以对HTTP数据加密是使用记录协议

    TLS记录协议主要负责消息(HTTP数据的压缩,加密数据的认证,过程如下图)

    具体过程如下:

    • 首先,消息被分割成多个较短的片段,然后分别对每个片段进行压缩。
    • 接下来,经过压缩的片段会被加上消息认证码(MAC 值,这个是通过哈希算法生成的),这是为了保证完整性,并进行数据的认证。通过附加消息认证码的 MAC 值,可以识别出篡改。与此同时,为了防止重放攻击,在计算消息认证码时,还加上了片段的编码。
    • 再接下来,经过压缩的片段再加上消息认证码会一起通过对称密码进行加密。
    • 最后,上述经过加密的数据再加上由数据类型、版本号、压缩后的长度组成的报头就是最终的报文数据。

    记录协议完成后,最终的报文数据将传递到传输控制协议 (TCP) 层进行传输。

    如果你想详细了解记录协议是如何分片、压缩、计算 MAC 值、分组加密,可以看这篇:理解SSL/TLS系列 (四) 记录协议

    HTTPS一定安全可靠吗?

    这个问题的场景是这样的:客户端通过浏览器向服务端发起 HTTPS 请求时,被「假基站」转发到了一个「中间人服务器」,于是客户端是和「中间人服务器」完成了 TLS 握手,然后这个「中间人服务器」再与真正的服务端完成 TLS 握手。

    • 客户端向服务端发起 HTTPS 建立连接请求时,然后被「假基站」转发到了一个「中间人服务器」,接着中间人向服务端发起 HTTPS 建立连接请求,此时客户端与中间人进行 TLS 握手,中间人与服务端进行 TLS 握手;
    • 在客户端与中间人进行 TLS 握手过程中,中间人会发送自己的公钥证书给客户端,客户端验证证书的真伪,然后从证书拿到公钥,并生成一个随机数,用公钥加密随机数发送给中间人,中间人使用私钥解密,得到随机数,此时双方都有随机数,然后通过算法生成对称加密密钥(A),后续客户端与中间人通信就用这个对称加密密钥来加密数据了。
    • 在中间人与服务端进行 TLS 握手过程中,服务端会发送从 CA 机构签发的公钥证书给中间人,从证书拿到公钥,并生成一个随机数,用公钥加密随机数发送给服务端,服务端使用私钥解密,得到随机数,此时双方都有随机数,然后通过算法生成对称加密密钥(B),后续中间人与服务端通信就用这个对称加密密钥来加密数据了。
    • 后续的通信过程中,中间人用对称加密密钥(A)解密客户端的 HTTPS 请求的数据,然后用对称加密密钥(B)加密 HTTPS 请求后,转发给服务端,接着服务端发送 HTTPS 响应数据给中间人,中间人用对称加密密钥(B)解密 HTTPS 响应数据,然后再用对称加密密钥(A)加密后,转发给客户端。

    从客户端的角度看,其实并不知道网络中存在中间人服务器这个角色.那么中间人就可以解开浏览器发起的HTTP请求里的数据,也可以解开服务端响应给浏览器的HTTPS响应数据.相当于中间人能够偷看浏览器与服务器之间的HTTPS请求和响应数据

    但是要发生这种场景是有前提的,前提是用户接受了中间人服务器的证书

    中间人服务器与客户端在 TLS 握手过程中,实际上发送了自己伪造的证书给浏览器,而这个伪造的证书是能被浏览器(客户端)识别出是非法的,于是就会提醒用户该证书存在问题。

    如果用户执意点击「继续浏览此网站」,相当于用户接受了中间人伪造的证书,那么后续整个 HTTPS 通信都能被中间人监听了。

    所以,这其实并不能说 HTTPS 不够安全,毕竟浏览器都已经提示证书有问题了,如果用户坚决要访问,那不能怪 HTTPS ,得怪自己手贱。

    另外,如果你的电脑中毒了,被恶意导入了中间人的根证书,那么在验证中间人的证书的时候,由于你操作系统信任了中间人的根证书,那么等同于中间人的证书是合法的,这种情况下,浏览器是不会弹出证书存在问题的风险提醒的。

    这其实也不关 HTTPS 的事情,是你电脑中毒了才导致 HTTPS 数据被中间人劫持的。

    所以,HTTPS 协议本身到目前为止还是没有任何漏洞的,即使你成功进行中间人攻击,本质上是利用了客户端的漏洞(用户点击继续访问或者被恶意导入伪造的根证书),并不是 HTTPS 不够安全

    为什么抓包工具能结局HTTPS数据

    很多抓包工具 之所以可以明文看到 HTTPS 数据,工作原理与中间人一致的。

    对于 HTTPS 连接来说,中间人要满足以下两点,才能实现真正的明文代理:

    1. 中间人,作为客户端与真实服务端建立连接这一步不会有问题,因为服务端不会校验客户端的身份;
    2. 中间人,作为服务端与真实客户端建立连接,这里会有客户端信任服务端的问题,也就是服务端必须有对应域名的私钥;

    中间人要拿到私钥只能通过如下方式:

    1. 去网站服务端拿到私钥;
    2. 去CA处拿域名签发私钥;
    3. 自己签发证书,切要被浏览器信任;

    不用解释,抓包工具只能使用第三种方式取得中间人的身份。

    使用抓包工具进行 HTTPS 抓包的时候,需要在客户端安装 Fiddler 的根证书,这里实际上起认证中心(CA)的作用。

    抓包工具能够抓包的关键是客户端会往系统受信任的根证书列表中导入抓包工具生成的证书,而这个证书会被浏览器信任,也就是抓包工具给自己创建了一个认证中心 CA,客户端拿着中间人签发的证书去中间人自己的 CA 去认证,当然认为这个证书是有效的。

    如何避免被中间人抓取数据?

    我们要保证自己电脑的安全,不要被病毒乘虚而入,而且也不要点击任何证书非法的网站,这样 HTTPS 数据就不会被中间人截取到了。

    当然,我们还可以通过 HTTPS 双向认证来避免这种问题。

    一般我们的 HTTPS 是单向认证,客户端只会验证了服务端的身份,但是服务端并不会验证客户端的身份。

    如果用了双向认证方式,不仅客户端会验证服务端的身份,而且服务端也会验证客户端的身份。服务端一旦验证到请求自己的客户端为不可信任的,服务端就拒绝继续通信,客户端如果发现服务端为不可信任的,那么也中止通信。

    HTTP/1.1,HTTP/2,HTTP/3演变

    HTTP/1.1提高的性能

    HTTP/1.1 相比 HTTP/1.0 性能上的改进:

    • 使用长连接的方式改善了 HTTP/1.0 短连接造成的性能开销。
    • 支持管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。

    但 HTTP/1.1 还是有性能瓶颈:

    • 请求 / 响应头部(Header)未经压缩就发送,首部信息越多延迟越大。只能压缩 Body 的部分;
    • 发送冗长的首部。每次互相发送相同的首部造成的浪费较多;
    • 服务器是按请求的顺序响应的,如果服务器响应慢,会招致客户端一直请求不到数据,也就是队头阻塞;
    • 没有请求优先级控制;
    • 请求只能从客户端开始,服务器只能被动响应。

    HTTP/2做了什么优化

    HTTP/2协议是基于HTTPS的,所以HTTP/2的安全性也是有保障的

    那 HTTP/2 相比 HTTP/1.1 性能上的改进:

    • 头部压缩
    • 二进制格式
    • 并发传输
    • 服务器主动推送资源

    头部压缩

    HTTP/2 会压缩头(Header)如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的部分

    这就是所谓的 HPACK 算法:在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。

    二进制格式

    HTTP/2不再像HTTP/1.1里面的纯文本形式的报文,而是采用了二进制格式,头信息和数据本地都是二进制,并且统称为帧(frame):头信息帧和数据帧

    这样虽然对人不友好,但是对计算机非常友好,因为计算机只懂二进制,那么收到报文后,无需再将明文的报文转成二进制,而是直接解析二进制报文,这增加了数据传输的效率

    并发传输

    我们都知道 HTTP/1.1 的实现是基于请求-响应模型的。同一个连接中,HTTP 完成一个事务(请求与响应),才能处理下一个事务,也就是说在发出请求等待响应的过程中,是没办法做其他事情的,如果响应迟迟不来,那么后续的请求是无法发送的,也造成了队头阻塞的问题。

    而 HTTP/2 就很牛逼了,引出了 Stream 概念,多个 Stream 复用在一条 TCP 连接。

    从上图可以看到,一个TCP连接包含多个Stream,Stream里面可以包含1个或多个Message,Message对应HTTP/1中的请求或相应,由HTTP头部和包体组成.MEssage里包含一条或者多个Frame,Frame是HTTP/2最小单位以二进制压缩格式存放HTTP/1中的内容(头部和包体)

    针对不同的 HTTP 请求用独一无二的 Stream ID 来区分,接收端可以通过 Stream ID 有序组装成 HTTP 消息,不同 Stream 的帧是可以乱序发送的,因此可以并发不同的 Stream ,也就是 HTTP/2 可以并行交错地发送请求和响应

    比如下图,服务端并行交错地发送了两个响应: Stream 1 和 Stream 3,这两个 Stream 都是跑在一个 TCP 连接上,客户端收到后,会根据相同的 Stream ID 有序组装成 HTTP 消息。

    HTTP/2 还在一定程度上改善了传统的「请求 - 应答」工作模式,服务端不再是被动地响应,可以主动向客户端发送消息。

    客户端和服务器双方都可以建立 Stream, Stream ID 也是有区别的,客户端建立的 Stream 必须是奇数号,而服务器建立的 Stream 必须是偶数号。

    比如下图,Stream 1 是客户端向服务端请求的资源,属于客户端建立的 Stream,所以该 Stream 的 ID 是奇数(数字 1);Stream 2 和 4 都是服务端主动向客户端推送的资源,属于服务端建立的 Stream,所以这两个 Stream 的 ID 是偶数(数字 2 和 4)。

    再比如,客户端通过 HTTP/1.1 请求从服务器那获取到了 HTML 文件,而 HTML 可能还需要依赖 CSS 来渲染页面,这时客户端还要再发起获取 CSS 文件的请求,需要两次消息往返,如下图左边部分:

    如上图右边部分,在 HTTP/2 中,客户端在访问 HTML 时,服务器可以直接主动推送 CSS 文件,减少了消息传递的次数。

    HTTP/2 通过 Stream 的并发能力,解决了 HTTP/1 队头阻塞的问题,看似很完美了,但是 HTTP/2 还是存在“队头阻塞”的问题,只不过问题不是在 HTTP 这一层面,而是在 TCP 这一层。

    HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。

    图中发送方发送了很多个 packet,每个 packet 都有自己的序号,你可以认为是 TCP 的序列号,其中 packet 3 在网络中丢失了,即使 packet 4-6 被接收方收到后,由于内核中的 TCP 数据不是连续的,于是接收方的应用层就无法从内核中读取到,只有等到 packet 3 重传后,接收方的应用层才可以从内核中读取到数据,这就是 HTTP/2 的队头阻塞问题,是在 TCP 层面发生的。

    所以,一旦发生了丢包现象,就会触发 TCP 的重传机制,这样在一个 TCP 连接中的所有的 HTTP 请求都必须等待这个丢了的包被重传回来

    HTTP/3做了哪些优化

    前面我们知道了 HTTP/1.1 和 HTTP/2 都有队头阻塞的问题:

    • HTTP/1.1 中的管道( pipeline)虽然解决了请求的队头阻塞,但是没有解决响应的队头阻塞,因为服务端需要按顺序响应收到的请求,如果服务端处理某个请求消耗的时间比较长,那么只能等响应完这个请求后, 才能处理下一个请求,这属于 HTTP 层队头阻塞。
    • HTTP/2 虽然通过多个请求复用一个 TCP 连接解决了 HTTP 的队头阻塞 ,但是一旦发生丢包,就会阻塞住所有的 HTTP 请求,这属于 TCP 层队头阻塞。

    HTTP/2 队头阻塞的问题是因为 TCP,所以 HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP!

    UDP发送是不管顺序的,也不管丢包的,所以不会出现想像HTTP/2队头阻塞的问题.大家都知道UDP是不可靠传输的,但是基于UDP的QUIC协议可以实现类似TCP的可靠传输

    QUIC 有以下 3 个特点。

    • 无队头阻塞
    • 更快的连接建立
    • 连接迁移

    无队头协议

    QUIC协议也有类似HTTP2/ Stream与多路复用的概念,也是可以在同一条连接上并发传输多个Stream与多路复用的概念,也是可以在同一个连接上并发传输多个Stream,Stream就可以认为自己是一条HTTP请求.

    QUIC有自己的一套机制可以保证传输的可靠性.当某个流发生丢包时,只会阻塞这个流,其他流不会受到影响,因此不存在队头阻塞问题.这与HTTP/2不同,HTTP/2只要某个流中的数据包丢失了,其他的流也会受此影响

    所以QUIC连接上的多个Stream之间并没有依赖,都是独立的,某个流发货所能丢包了, 只会影响该流,其他流不受影响

    更快的链接建立

    对于 HTTP/1 和 HTTP/2 协议,TCP 和 TLS 是分层的,分别属于内核实现的传输层、openssl 库实现的表示层,因此它们难以合并在一起,需要分批次来握手,先 TCP 握手,再 TLS 握手。

    HTTP/3 在传输数据前虽然需要 QUIC 协议握手,但是这个握手过程只需要 1 RTT,握手的目的是为确认双方的「连接 ID」,连接迁移就是基于连接 ID 实现的。

    但是 HTTP/3 的 QUIC 协议并不是与 TLS 分层,而是 QUIC 内部包含了 TLS,它在自己的帧会携带 TLS 里的“记录”,再加上 QUIC 使用的是 TLS/1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与密钥协商,如下图:

    如下图右边部分,HTTP/3 当会话恢复时,有效负载数据与第一个数据包一起发送,可以做到 0-RTT(下图的右下角):

    链接迁移

    基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的 IP、目的端口)确定一条 TCP 连接。

    那么当移动设备的网络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立连接。而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程,给用户的感觉就是网络突然卡顿了一下,因此连接的迁移成本是很高的。

    而 QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID 来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。

    所以, QUIC 是一个在 UDP 之上的 TCP + TLS + HTTP/2 的多路复用的协议。

    QUIC 是新协议,对于很多网络设备,根本不知道什么是 QUIC,只会当做 UDP,这样会出现新的问题,因为有的网络设备是会丢掉 UDP 包的,而 QUIC 是基于 UDP 实现的,那么如果网络设备无法识别这个是 QUIC 包,那么就会当作 UDP包,然后被丢弃。

    HTTP/3 现在普及的进度非常的缓慢,不知道未来 UDP 是否能够逆袭 TCP。

    Last modification:April 20, 2023
    如果觉得我的文章对你有用,请随意赞赏