HTTP协议[💖]

参考资料:
https://www.cnblogs.com/xiaolincoding/p/12442435.html
https://www.cnblogs.com/rickiyang/p/13160849.html
https://segmentfault.com/a/1190000021494676
https://segmentfault.com/a/1190000017831088#comment-area
https://juejin.cn/post/6844904100035821575
https://juejin.cn/post/6844904100035821575#heading-8
https://github.com/CyC2018/CS-Notes/blob/master/notes/HTTP.md

HTTP (HyperText Transfer Protocol),全称超⽂文本传输协议。其中常见的文本,图片,视频这些东西都可以用超文本进行表示。

基本特性

  • HTTP是基于请求/响应模型的协议。请求和响应必须成对,先有请求后有响应。
  • HTTP 是无连接的: HTTP客户端,即浏览器发出请求后,客户端等待响应。服务器处理该请求并发送回响应,然后客户端断开连接。客户端和服务器仅在当次请求中互相了解,至于上一次是否有连接或者连接的信息是无从得知的。
  • HTTP是独立于媒体的: 这意味着,只要客户端和服务器都知道如何处理数据内容,任何类型的数据都可以通过HTTP发送。客户端和服务器都需要使用适当的 MIME 类型 指定内容类型。
  • HTTP是无状态的: 如上所述,HTTP 是无连接的,这是 HTTP 是无状态协议的直接结果。服务器和客户端仅在当前请求期间彼此知道,之后他们俩彼此忘记。由于协议的这种性质,客户端和浏览器都无法在整个网页的不同请求之间保留信息。
    HTTP是无状态的:在同一个连接中,两个执行成功的请求之间是没有关系的。无状态协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息。
    无状态协议解决办法: 通过Cookie、通过Session会话保存。
    https://www.cnblogs.com/bellkosmos/p/5237146.html

HTTP报文结构

1. 通用首部

image-20220322190846883.png
Cache-Control 通用消息头字段,被用于在http请求和响应中,通过指定指令来实现缓存机制。缓存指令是单向的,这意味着在请求中设置的指令,不一定被包含在响应中。

  • 缓存请求指令:客户端可以在HTTP请求中使用的标准 Cache-Control 指令。
    1. Cache-Control: max-age=<seconds>
    2. Cache-Control: max-stale[=<seconds>]
    3. Cache-Control: min-fresh=<seconds>
    4. Cache-control: no-cache
    5. Cache-control: no-store
    6. Cache-control: no-transform
    7. Cache-control: only-if-cached
  • 缓存响应指令:服务器可以在响应中使用的标准 Cache-Control 指令。
    Cache-control: must-revalidate
    Cache-control: no-cache
    Cache-control: no-store
    Cache-control: no-transform
    Cache-control: public
    Cache-control: private
    Cache-control: proxy-revalidate
    Cache-Control: max-age=<seconds>
    Cache-control: s-maxage=<seconds>
    
  • 可缓存性
    no-cache:并不代表完全的禁用缓存,而是代表会每次去核对服务端的 Etag,如果相同,那么就不会去服务端下载完整的资源,返回一个 304 Not Modified。(最长缓存 3 年)。
    no-store:才是真正的禁用缓存,它表示每次服务端都会去下载最新的资源。
    public 和 private 的差别主要在于如果是有用户认证环节的页面,设置为private 就只有终端浏览器会缓存,中间 CDN 并不会缓存,而设置为 public,则会在每一个环节缓存。默认不需要设置 public,因为 max-age 已经表明可以由各个环节缓存了(单位为秒)。此刻如果命中缓存,则不会再去请求服务器核对 Etag,而是直接返回 200。
  • 到期
    max-age=<seconds>:设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。与Expires相反,时间是相对于请求的时间。
    s-maxage=<seconds>:覆盖max-age或者Expires头,但是仅适用于共享缓存(比如各个代理),私有缓存会忽略它。
    min-fresh=<seconds>:表示客户端希望获取一个能在指定的秒数内保持其最新状态的响应。
    stale-while-revalidate=<seconds>:表明客户端愿意接受陈旧的响应,同时在后台异步检查新的响应。秒值指示客户愿意接受陈旧响应的时间长度。
    stale-if-error=<seconds>:表示如果新的检查失败,则客户愿意接受陈旧的响应。秒数值表示客户在初始到期后愿意接受陈旧响应的时间。
  • 重新验证和重新加载
    must-revalidate:一旦资源过期(比如已经超过max-age),在成功向原始服务器验证之前,缓存不能用该资源响应后续请求。
    proxy-revalidate:与must-revalidate作用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略。
    immutable:表示响应正文不会随时间而改变。资源(如果未过期)在服务器上不发生改变,因此客户端不应发送重新验证请求头(例如If-None-Match或If-Modified-Since)来检查更新,即使用户显式地刷新页面。

2. 请求报文格式

image-20220321123140877.png

  • 回车(Carriage Return, ‘\r’)是指光标回到当前行行首;换行(Line Feed,’\n’)是指另起一行,光标在新行的行头;
  • Windows采用回车+换行(CR+LG)表示下一行(亦即所谓的PC格式);
  • UNIX/Linux采用换行符(LF)表示下一行;
  • MAC机采用回车符(CR)表示下一行。

3. HTTP 的请求方法

  • GET: 请求获取Request-URI所标识的资源
  • POST: 在Request-URI所标识的资源后附加新的数据
  • PUT:请求服务器存储一个资源,并用Request-URI作为其标识
  • HEAD: 请求获取由Request-URI所标识的资源的响应消息报头
  • DELETE: 请求服务器删除Request-URI所标识的资源
  • OPTIONS:询问支持的方法
  • TRACE: 追踪路径TRACE方法是让Web服务器端将之前的请求通信环回给客户端的方法。
    • 发送请求时,在Max-Forwards首部字段中填入数值,每经过一个服务器端就将该数字减1,当数值刚好减到0时,就停止继续传输,最后接收到请求的服务器端则返回状态码200 OK的响应。
  • CONNECT: 要求用隧道协议链接代理

4. GET和POST区别[💖💖]

  • 缓存: GET 请求会被浏览器主动缓存下来,留下历史记录;而 POST 默认不会,除非手动设置
  • GET产生的URL地址可以被Bookmark,而POST不可以。
  • 编码: GET 只能进行 URL 编码,只能接收 ASCII 字符,而 POST 没有限制。
  • 参数: GET 参数一般放在 URL 中,因此不安全,POST 放在请求体中,更适合传输敏感信息。
    GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留
  • 幂等: GET是安全且幂等的,因为它是「只读」操作,无论操作多少次,服务器上的数据都是安全的,且每次的结果都是相同的。
    而POST是「新增或提交数据」的操作,会修改服务器上的资源,所以是不安全的,且多次提交数据就会创建多个资源,所以不是幂等的。
    安全是指请求方法不会破坏服务器上的资源;幂等是指多次执行相同的操作,结果也是相同的。
  • TCP: GET产生一个TCP数据包;POST产生两个TCP数据包
    GET请求会把浏览器会把http header和body一次性发出去,服务器响应200(返回数据);
    而POST会分成两个TCP数据包,首先发Header部分,如果服务器响应100(continue), 然后发 body 部分。(火狐浏览器除外,它的 POST 请求只发一个 TCP 包)

5. 响应报文格式

image-20220321123213296.png

6. 状态码

参考资料:[http://tools.jb51.net/table/http_status_code](http://tools.jb51.net/table/http_status_code)<br />    HTTP Status Code是常说的HTTP状态码。当用户访问一个网页时,浏览器会向网页所在服务器发出请求。服务器则会根据请求作出响应,而状态码则是响应的一部分,代表着本次请求的结果。所有状态码的第一个数字代表了响应的大概含义,组合上第二第三个数字则可以表示更具体的原因。如果请求失败了,通过这个状态码,大概初步判断出这次请求失败的原因。以下是五类状态码的含义。<br />![image-20220321125941630.png](https://cdn.nlark.com/yuque/0/2022/png/12923355/1650028992860-fcfead18-48e5-462c-8a09-c6ef51aeeb55.png#clientId=u69e92246-7fef-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=372&id=u195dc38f&margin=%5Bobject%20Object%5D&name=image-20220321125941630.png&originHeight=372&originWidth=901&originalType=binary&ratio=1&rotation=0&showTitle=false&size=705031&status=done&style=shadow&taskId=uf68a9662-1d4e-43b8-9749-5961982e4e1&title=&width=901)
1xx
100 : Continue    继续。客户端应继续其请求
101    : Switching Protocols    切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议

2XX 成功
200 :OK 一切正常,对GET和POST请求的应答文档跟在后面。
201 :Created 服务器已经创建了文档,Location头给出了它的URL。
202 :Accepted 已经接受请求,但处理尚未完成。
203 :Non-Authoritative Information 文档已经正常地返回,但一些应答头可能不正确,因为使用的是文档的拷贝(HTTP /1.1新)。
204 :No Content 没有新文档,浏览器应该继续显示原来的文档。如果用户定期地刷新页面,而Servlet可以确定用户文档足够新,这个状态代码是很有用的。
205 :Reset Content 没有新的内容,但浏览器应该重置它所显示的内容。用来强制浏览器清除表单输入内容(HTTP/ 1.1新)。
206 :Partial Content 客户发送了一个带有 Range 头的 GET 请求,服务器完成了它(HTTP /1.1新)。


3XX 重定向
300 :Multiple Choices 客户请求的文档可以在多个位置找到,这些位置已经在返回的文档内列出。如果服务器要提出优先选择,则应该在 Location 应答头指明。
301 :Moved Permanently 客户请求的文档在其他地方,新的 URL 在 Location 头中给出,浏览器应该自动地访问新的 URL。
302 :Found 类似于 301,但新的 URL 应该被视为临时性的替代,而不是永久性的。注意,在 HTTP1.0 中对应的状态信息是“Moved Temporatily”。出现该状态代码时,浏览器能够自动访问新的 URL,因此它是一个很有用的状态代码。
303 :See Other 类似于 301 / 302,不同之处在于,如果原来的请求是 POST,Location 头指定的重定向目标文档应该通过GET提取(HTTP 1.1新)。
304 :Not Modified 客户端有缓冲的文档并发出了一个条件性的请求(一般是提供 If-Modified-Since 头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。
305 :Use Proxy 客户请求的文档应该通过 Location 头所指明的代理服务器提取(HTTP /1.1新)。
307 :Temporary Redirect 和 302(Found)相同。许多浏览器会错误地响应 302 应答进行重定向,即使原来的请求是 POST,即使它实际上只能在 POST 请求的应答是 303 时才能重定向。
      由于这个原因,HTTP/1.1 新增了 307,以便更加清楚地区分几个状态代码:当出现 303 应答时,浏览器可以跟随重定向的 GET 和 POST 请求;如果是 307 应答,则浏览器只能跟随对 GET 请求的重定向。(HTTP/ 1.1新)


4XX 客户端错误
400 :Bad Request 请求出现语法错误。
401 :Unauthorized 客户试图未经授权访问受密码保护的页面。应答中会包含一个WWW-Authenticate 头,浏览器据此显示用户名字/密码对话框,然后在填写合适的 Authorization 头后再次发出请求。
403 :Forbidden 资源不可用。服务器理解客户的请求,但拒绝处理它。通常由于服务器上文件或目录的权限设置导致。
404 :Not Found 无法找到指定位置的资源。这也是一个常用的应答。
405 :Method Not Allowed 请求方法(GET、POST、HEAD、DELETE、PUT、TRACE等)对指定的资源不适用。(HTTP /1.1新) 
406 :Not Acceptable 指定的资源已经找到,但它的 MIME 类型和客户在 Accpet 头中所指定的不兼容(HTTP /1.1新)。
407 :Proxy Authentication Required 类似于 401,表示客户必须先经过代理服务器的授权。(HTTP /1.1新)。
408 :Request Timeout 在服务器许可的等待时间内,客户一直没有发出任何请求。客户可以在以后重复同一请求。(HTTP/ 1.1新)
409 :Conflict 通常和PUT请求有关。由于请求和资源的当前状态相冲突,因此请求不能成功。(HTTP /1.1新)
410 :Gone 所请求的文档已经不再可用,而且服务器不知道应该重定向到哪一个地址。它和404的不同在于,返回407表示文档永久地离开了指定的位置,而404表示由于未知的原因文档不可用。(HTTP/ 1.1新)。
411 :Length Required 服务器不能处理请求,除非客户发送一个 Content-Length 头。(HTTP /1.1新)
412 :Precondition Failed 请求头中指定的一些前提条件失败(HTTP /1.1新)。
413 :Request Entity Too Large 目标文档的大小超过服务器当前愿意处理的大小。如果服务器认为自己能够稍后再处理该请求,则应该提供一个 Retry-After 头(HTTP/ 1.1新)。
414 :Request URI Too Long URI 太长(HTTP/ 1.1新)。
416 :Requested Range Not Satisfiable 服务器不能满足客户在请求中指定的 Range 头。(HTTP /1.1新)


5XX 服务器端错误
500:Internal Server Error 服务器故障。
503:Service Unavailable 服务器处于超负载或正在停机维护。

状态码流程
image-20220321130101649.png

数据类型与编码

HTTP采用**多用途互联网邮件扩展** (Multipurpose Internet Mail Extensions,MIME)来标记 body 的数据类型,这就是我们平常总能听到的 **MIME type**。Content-Type常用的类型如下:
  1. text:即文本格式的可读数据。 text/html : 表示超文本文档;text/plain:纯文本; text/css :样式表;text/javascript :js格式; text/xml:POST专用,发送xml数据
  2. image:即图像文件。有 image/gifimage/jpegimage/png 等。
  3. audio/video:音频和视频数据。例如 audio/mpegvideo/mp4 等。
  4. application:数据格式不固定,可能是文本也可能是二进制,必须由上层应用程序来解释。常见的有 application/jsonapplication/javascriptapplication/pdf 等,另外,如果实在是不知道数据是什么类型,像刚才说的 黑盒 ,就会是 application/octet-stream,即不透明的二进制数据。
    application/x-www-form-urlencoded: POST专用。普通的表达默认提交是通过这种方式,form表达数据被编码为key/value格式

大文件传输问题[💖]

数据压缩

HTTP在传输时为了节约带宽,还会压缩数据。浏览器在发送请求时都会带着 **Accept-Encoding**头字段,
里面是浏览器支持的压缩格式列表,如gzip、deflate、br 等,这样服务器就可以从中选择一种压缩算法,
放进 **Content-Encoding** 响应头里,再把原数据压缩后发给浏览器。

缺点:gzip 等压缩算法通常只对文本文件有较好的压缩率,而图片、音频视频等多媒体数据本身就已经是高度压缩的,再用 gzip 处理也不会变小(甚至还有可能会增大一点),所以它就失效了。

分块传输

压缩是把大文件整体变小,如果大文件整体不能变小,那就把它 \拆开 ,分解成多个小块,把这些小块分批发给浏览器,浏览器收到后再组装复原。这种 **化整为零** 的思路在 HTTP 协议里就是 **chunked** 分块传输编码,在响应报文里用头字段 **Transfer-Encoding: chunked** 来表示,意思是报文里的 body 部分不是一次性发过来的,而是分成了许多的块(chunk)逐个发送。<br />    分块传输也可以用于 流式数据 ,例如由数据库动态生成的表单页面,这种情况下 body 数据的长度是未知的,无法在头字段 **Content-Length** 里给出确切的长度,所以也只能用 chunked 方式分块发送。<br />    `Transfer-Encoding: chunked` 和 `Content-Length` 这两个字段是**互斥的**,也就是说响应报文里这两个字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知(chunked)。<br />    分块传输的编码规则:
  1. 每个分块包含两个部分,长度头和数据块;
  2. 长度头是以 CRLF(回车换行,即\r\n)结尾的一行明文,用 16 进制数字表示长度;
  3. 数据块紧跟在长度头后,最后也用 CRLF 结尾,但数据不包含 CRLF;
  4. 最后用一个长度为 0 的块表示结束,即 0\r\n\r\n 。

image-20220321192851428.png

范围请求

对于上 G 的超大文件,比如观看当下正热播的某穿越剧,想跳过片头,直接看正片,或者有段剧情很无聊,想拖动进度条快进几分钟,这实际上是想获取一个大文件其中的片段数据,而分块传输并没有这个能力。<br />    HTTP 协议为了满足这样的需求,提出了 **范围请求** (range requests)的概念,允许客户端在请求头里使用专用字段来表示只获取文件的一部分,相当于是**客户端的化整为零 **。<br />    范围请求不是 Web 服务器必备的功能,可以实现也可以不实现,所以服务器必须在响应头里使用字段 **Accept-Ranges: bytes** 明确告知客户端: 我是支持范围请求的 。<br />    如果不支持的话服务器可以发送 `Accept-Ranges: none`,或者干脆不发送 `Accept-Ranges` 字段,这样客户端就认为服务器没有实现范围请求功能,只能老老实实地收发整块文件了。<br />    请求头 **Range** 是 HTTP 范围请求的专用字段,格式是 **bytes=x - y** ,其中的 x 和 y 是以字节为单位的数据范围。要注意 x、y 表示的是偏移量 ,范围必须从 0 计数,例如前 10 个字节表示为 0-9。Range 的格式也很灵活,起点 x 和终点 y 可以省略,能够很方便地表示正数或者倒数的范围。假设文件是 100 个字节,那么:
  • 0- 表示从文档起点到文档终点,相当于 0-99 ,即整个文件;
  • 10- 是从第 10 个字节开始到文档末尾,相当于 10-99 ;
  • -1 是文档的最后一个字节,相当于 99-99 ;
  • -10 是从文档末尾倒数 10 个字节,相当于 90-99 。

服务器收到 Range 字段后,需要做四件事。

  1. 它必须检查范围是否合法,比如文件只有 100 个字节,但请求 200-300 ,这就是范围越界了。服务器就会返回状态码 416,意思是你的范围请求有误我无法处理,请再检查一下 。
  2. 如果范围正确,服务器就可以根据 Range 头计算偏移量,读取文件的片段了,返回状态码 206 Partial Content ,和 200 的意思差不多,但表示 body 只是原数据的一部分。
  3. 服务器要添加一个响应头字段 Content-Range,告诉片段的实际偏移量和资源的总大小,格式是 bytes x-y/length ,与 Range 头区别在没有 = ,范围后多了总长度。例如,对于 0-10 的范围请求,值就是 bytes 0-10/100 。
  4. 最后剩下的就是发送数据,直接把片段用 TCP 发给客户端,一个范围请求就算是处理完了。

多段数据

前述的范围请求一次只获取一个片段,其实它还支持在 Range 头里使用多个 x - y ,一次性获取多个片段数据。该情况需要使用特殊的 MIME 类型: **multipart/byteranges** ,表示报文的 body 是由多段字节序列组成的,并且还要用一个参数 **boundary=xxx** 给出段之间的分隔标记。多段数据的格式与分块传输也比较类似,但它需要用分隔标记 boundary 来区分不同的片段,可以通过图来对比一下。<br />![image-20220321210012660.png](https://cdn.nlark.com/yuque/0/2022/png/12923355/1650029244190-0feeb85d-70b2-4cb1-8426-e6cf758515e4.png#clientId=u69e92246-7fef-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=574&id=u0f8e85bb&margin=%5Bobject%20Object%5D&name=image-20220321210012660.png&originHeight=574&originWidth=1019&originalType=binary&ratio=1&rotation=0&showTitle=false&size=184935&status=done&style=shadow&taskId=u30d7b0fb-aeb6-4ada-aa3c-050432b1721&title=&width=1019)<br />    每一个分段必须以 `--boundary` 开始(前面加两个 - ),之后要用 `Content-Type` 和 `Content-Range` 标记这段数据的类型和所在范围,然后就像普通的响应头一样以回车换行结束,再加上分段数据,最后用一个 `--boundary--`(前后各有两个 - )表示所有的分段结束。<br />    **要注意这四种方法不是互斥的,而是可以混合起来使用,例如压缩后再分块传输,或者分段后再分块**。

缓存控制

[https://juejin.cn/post/6993358764481085453#heading-18](https://juejin.cn/post/6993358764481085453#heading-18)

Cookie和Session[💖]

参考资料:
https://segmentfault.com/a/1190000017831088#comment-area
https://juejin.cn/post/6844904034181070861#comment

**HTTP 是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息**):每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。而这个状态需要通过 cookie 或者 session 去实现用户识别和状态信息的管理。

Cookie是服务器发送到用户浏览器并保存在**本地**的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。它通常用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。一般来说,**单个域名设置的 Cookie 不应超过20个,每个 Cookie 的大小不能超过4KB**。超过限制以后,Cookie 将被忽略,不会被设置。Cookie 主要用于以下三个方面:
  • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
  • 个性化设置(如用户自定义设置、主题等)
  • 浏览器行为跟踪(如跟踪分析用户行为等)

Cookie的工作过程

当用户通过浏览器第一次访问服务器的时候,服务器肯定是不知道他的身份的。所以,就要创建一个独特的身份标识数据,格式是 **key=value** ,然后放进 Set-Cookie 字段里,随着响应报文一同发给浏览器。<br />    浏览器收到响应报文,看到里面有 Set-Cookie,知道这是服务器给的身份标识,于是就保存起来,下次再请求的时候就自动把这个值放进 Cookie 字段里发给服务器。<br />    因为第二次请求里面有了 Cookie 字段,服务器就知道这个用户不是新人,之前来过,就可以拿出 Cookie 里的值,识别出用户的身份,然后提供个性化的服务。

Cookie的重要属性

属性 说明
name=value 键值对,设置 Cookie 的名称及相对应的值,都必须是字符串类型
- 如果值为 Unicode 字符,需要为字符编码。
- 如果值为二进制数据,则需要使用 BASE64 编码。
Domain 指定 cookie 所属域名,默认是当前域名
Path 指定 cookie 在哪个路径(路由)下生效,默认是 ‘/‘
如果设置为 /abc,则只有 /abc下的路由可以访问到该 cookie,
如:/abc/read
Max-Age cookie 失效的时间,单位秒。如果为整数,则该 cookie 在 maxAge 秒后失效。
如果为负数,该 cookie 为临时 cookie ,关闭浏览器即失效,浏览器也不会以任何形式保存该 cookie 。
如果为 0,表示删除该 cookie 。默认为 -1。
ExpiresMax-Age可以同时出现且时间可以不一致,优先采用Max-Age。
Expires 过期时间,在设置的某个时间点后该 cookie 就会失效。
一般浏览器的 cookie 都是默认储存的,当关闭浏览器结束这个会话的时候,这个 cookie 也就会被删除。
Secure 该 cookie 是否仅被使用安全协议传输。安全协议有 HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false。
当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效。
HttpOnly 如果给某个 cookie 设置了 httpOnly 属性,则无法通过 JS 脚本(document.cookie)读写到 cookie 数据,但还是能通过 Application 中手动修改 cookie,所以只是在一定程度上可以防止 XSS 攻击,不是绝对的安全
SameSite 可以防范 跨站请求伪造 (XSRF)攻击,设置成SameSite=Strict
可以严格限定 Cookie 不能随着跳转链接跨站发送,而 SameSite=Lax
则略宽松一点,允许 GET/HEAD 等安全方法,但禁止 POST 跨站发送。

Session原理

当用户第一次访问Servlet时,服务器端会给用户创建一个独立的Session并且生成一个SessionID,这个SessionID在响应浏览器的时候会被装进cookie中,从而被保存到浏览器中当用户再一次访问Servlet时,请求中会携带着cookie中的SessionID去访问服务器会根据这个SessionID去查看是否有对应的Session对象有就拿出来使用;没有就创建一个Session(相当于用户第一次访问)。
session 认证流程:

  • 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session
  • 请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器
  • 浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
  • 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。

image-20220323112411022.png

cookie和session的区别?[💖💖]

  • 存储位置: cookie数据存放在客户的浏览器上,session数据放在服务器上
  • 隐私策略(安全性):cookie不是很安全, 别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session
  • 有效期:Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能;Session 一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。
  • 存储大小不同: 单个cookie保存的数据不能超过4k, 很多浏览器都限制一个站点最多保存20个cookie。

cookie中适合放哪些信息?

cookie的增多无疑会加重网络请求的开销,而且每次请求都会将cookie完整的带上,因此对于那些“每次请求都必须要携带的信息(如身份信息、A/B分桶信息等)”,才适合放进cookie中,其他类型的数据建议放进localStorage中

session级别的cookie

session级别的cookie只针对当前会话存在,会话终止则cookie消失。当cookie没有设置expires的时候,该cookie只会在网页会话期间存在,当浏览器退出的时候,该cookie就会消失。浏览器中的表现为Expires/Max-Age的内容为Session。

cookie可以被谁来操作?

服务端和js都可以读/写cookie。js和服务端对cookie的操作区别在于:cookie有一个属性是HttpOnly,HttpOnly被设置时,表明该cookie只能被http请求读取,不能被js读取,具体的表现是:document.cookie读取到的内容不包含设置了HttpOnly的cookie。<br />    js操作读/写cookie的api是document.cookie,读写cookie框架参考:[https://developer.mozilla.org/zh-CN/docs/Web/API/Document/cookie](https://developer.mozilla.org/zh-CN/docs/Web/API/Document/cookie)
// 读cookie`
document.cookie

// 写cookie`
document.cookie = newCookie;    
newCookie是一个键值对形式的字符串:
;path=path (例如 '/', '/mydir') 如果没有定义,默认为当前文档位置的路径。
;domain=domain (例如 'example.com', '.example.com' (包括所有子域名),'subdomain.example.com') 如果没有定义,默认为当前文档位置的路径的域名部分。
;max-age=max-age-in-seconds (例如一年为60*60*24*365),这项设置比expires的优先级高;
;expires=date-in-GMTString-format 如果没有定义,cookie会在对话结束时【浏览器关闭后删除】过期,这个值的格式参见Date.toUTCString() 
;secure (cookie只通过https协议传输)

// 将一个已经存在的cookie名字过期时间(expires)设置为过去的时间用来删除cookie`
document.cookie = 'uid=dkfywqkrhkwehf23;expires=' + new Date(0) + ';path=/;secure;'

cookie 的共享策略是什么?

**cookie 是不可跨域的**。每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用,**一级域名和二级域名之间是允许通过domain共享使用。**<br />    domain和path共同决定了cookie可以被哪些url访问。访问一个url时,如果url的host与domain一致或者是domain的子域名,并且url的路径与path部分匹配,那么cookie才可以被读取。<br />![image-20220322214516346.png](https://cdn.nlark.com/yuque/0/2022/png/12923355/1650029643383-aa96cc47-2f93-4cef-85bc-446689d93578.png#clientId=u69e92246-7fef-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=132&id=ub08f43c8&margin=%5Bobject%20Object%5D&name=image-20220322214516346.png&originHeight=132&originWidth=1064&originalType=binary&ratio=1&rotation=0&showTitle=false&size=110747&status=done&style=shadow&taskId=u0fd9e8ef-dec4-4fd3-a56a-8cc2fad05bb&title=&width=1064)<br />    假如cookie如上图所示,那么一个url与可读取的cookie对应关系为:
url uid1 uid2 uid3
https://edu.360.cn/caikuai/
https://edu.360.cn/ ×
https://360.cn/article/123.html × ×

Cookie与Web安全

  • cookie如何应对XSS漏洞?
    XSS(Cross-site scripting), 跨站脚本攻击的简称。其原理是:由于未对用户提交的表单数据或者url参数等数据做处理就显示在了页面上,导致用户提交的内容在页面上被做为html解析执行。
    常规方案:对特殊字符进行处理,如”<”和”>”等进行转义。
    cookie的应对方案
    :对于用户利用script脚本来采集cookie信息,我们可以将重要的cookie信息设置为HttpOnly来避免cookie被js采集
  • cookie如何应对CSRF攻击?
    CSRF(Cross-site request forgery),跨站请求伪造的简称。其原理是:用户登陆了A网站,然后因为某些原因访问了B网站(比如跳转等),B网站直接发送一个A网站的请求进行一些危险操作,由于A网站处于登陆状态,就发生了CSRF攻击(核心就是利用了cookie信息可以被跨站携带)!
    常规方案:采用验证码或token等。
    cookie的应对方案:由于CSRF攻击核心就是利用了cookie信息可以被跨站携带,那么我们可以对核心cookie的SameSite设置为Strict或Lax来避免。

另外两种本地存储方式(HTML5提供)

  • **localStorage**
    localStorage目前只支持存储字符串,其他格式均会被转换为字符串来存储,想要存储其他可以考虑JSON.parse/JSON.stringify配合使用。有以下特点:
    • 单个域名存储量比较大(推荐5MB,各浏览器不同)总体数量无限制
    • 浏览器关闭不清除。页面之间可以实现共享。同一域名下实现共享,【顶级域名和二级/三级域名不共享】
    • 只要不清除。可以在本地永久储存。

有以下API用来使用localStorage:

localStorage.getItem(key)                 // 根据键key来读取值vakue的方法.推荐使用
localStorage.setItem(key, value)     // 写入键-值对的方法.推荐使用
localStorage.removeItem(key)             // 根据键key来删除某一键值对.推荐使用
localStorage.clear()                             // 清空
localStorage.key(index)                     // 根据索引值返回对应的key
  • **sessionStorage**

    sessionStorage属性允许你访问一个 session Storage 对象。它与 localStorage 相似,不同之处在于 localStorage 里面存储的数据没有过期时间设置,而存储在 sessionStorage 里面的数据在页面会话结束时会被清除。sessionStorage的特点:

  • 页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。

  • 在新标签或窗口打开一个页面时会复制顶级浏览会话的上下文作为新会话的上下文,这点和 session cookies 的运行方式不同。
  • 打开多个相同的 URL 的 Tabs 页面,会创建各自的 sessionStorage
  • 关闭对应浏览器标签或窗口,会清除对应的 sessionStorage
    应该注意的是,无论是 localStorage 还是sessionStorage 中保存的数据都仅限于该页面的协议。也就是说 http://example.comhttps://example.com 的 sessionStorage 相互隔离。
let data = sessionStorage.getItem('key');    // 从 sessionStorage 获取键为'key'的数据
sessionStorage.setItem('key', 'value');        // 保存数据到 sessionStorag
sessionStorage.removeItem('key');                    // 从 sessionStorage 删除保存的数据
sessionStorage.clear();                                        // 从 sessionStorage 删除所有保存的数据

Cookie、localStorage、sessionStorage的区别:
image-20220322215732138.png

使用 cookie 时需要考虑的问题

  • 因为存储在客户端,容易被客户端篡改,使用前需要验证合法性
  • 不要存储敏感数据,比如用户密码,账户余额
  • 使用 httpOnly 在一定程度上提高安全性
  • 尽量减少 cookie 的体积,能存储的数据量不能超过 4kb
  • 设置正确的 domain 和 path,减少数据传输
  • cookie 无法跨域
  • 一个浏览器针对一个网站最多存 20 个Cookie,浏览器一般只允许存放 300 个Cookie
  • 移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token

使用 session 时需要考虑的问题

  • 将 session 存储在服务器里面,当用户同时在线量比较多时,这些 session 会占据较多的内存,需要在服务端定期的去清理过期的 session
  • 当网站采用集群部署的时候,会遇到多台 web 服务器之间如何做 session 共享的问题。因为 session 是由单个服务器创建的,但是处理用户请求的服务器不一定是那个创建 session 的服务器,那么该服务器就无法拿到之前已经放入到 session 中的登录凭证之类的信息了。
  • 当多个应用要共享 session 时,除了以上问题,还会遇到跨域问题,因为不同的应用可能部署的主机不一样,需要在各个应用做好 cookie 跨域的处理。
  • sessionId 是存储在 cookie 中的,假如浏览器禁止 cookie 或不支持 cookie 怎么办? 一般会把 sessionId 跟在 url 参数后面即重写 url,所以 session 不一定非得需要靠 cookie 实现
  • 移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token

Token 和 JWT[💖]

认证(Authentication)

认证通俗地讲就是**验证当前用户的身份**,证明“你是你自己”。互联网种需要用到认证的地方有:
  • 用户名密码登录;
  • 邮箱发送登录链接;
  • 手机号接收验证码;
  • 只要你能收到邮箱/验证码,就默认你是账号的主人。

授权(Authorization)

**授权就是用户授予第三方应用访问该用户某些资源的权限**
  • 你在安装手机应用的时候,APP 会询问是否允许授予权限(访问相册、地理位置等权限)
  • 你在访问微信小程序时,当登录时,小程序会询问是否允许授予权限(获取昵称、头像、地区、性别等个人信息)

    实现授权的方式有:cookie、session、token、OAuth。

凭证(Credentials)

**实现认证和授权的前提**是需要一种**媒介(证书)** 来标记访问者的身份。<br />    在互联网应用中,一般网站(如掘金)会有两种模式,游客模式和登录模式。游客模式下,可以正常浏览网站上面的文章,一旦想要点赞/收藏/分享文章,就需要登录或者注册账号。当用户登录成功后,服务器会给该用户使用的浏览器颁发一个令牌(token),这个令牌用来表明你的身份,每次浏览器发送请求时会带上这个令牌,就可以使用游客模式下无法使用的功能。

Token

token 也称作令牌,由uid+time+sign[+固定参数]构成:

  • uid: 用户唯一身份标识
  • time: 当前时间的时间戳
  • sign: 签名, 使用 hash/encrypt 压缩成定长的十六进制字符串,以防止第三方恶意拼接
  • 固定参数(可选): 将一些常用的固定参数加入到 token 中是为了避免重复查库

token 的认证方式类似于临时的证书签名, 并且是一种服务端无状态的认证方式, 非常适合于 REST API 的场景. 所谓无状态就是服务端并不会保存身份认证相关的数据。token在客户端一般存放于localStorage,cookie,或sessionStorage中。在服务器一般存于数据库中。
token 的认证流程与cookie很相似

  • 用户登录,成功后服务器返回Token给客户端。
  • 客户端收到数据后保存在客户端
  • 客户端再次访问服务器,将token放入headers中
  • 服务器端采用filter过滤器校验。校验成功则返回请求数据,校验失败则返回错误码

token可以抵抗csrf,cookie+session不行。
image-20220323112257916.png

Refresh Token

refresh token 是专用于刷新 access token 的 token。如果没有 refresh token,也可以刷新 access token,但每次刷新都要用户输入登录用户名与密码,会很麻烦。有了 refresh token,可以减少这个麻烦,客户端直接用 refresh token 去更新 access token,无需用户进行额外的操作。<br />![image-20220323161232522.png](https://cdn.nlark.com/yuque/0/2022/png/12923355/1650029961267-948e80b1-aa79-49e7-8428-91c87cbf4737.png#clientId=u69e92246-7fef-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=897&id=ub3d30349&margin=%5Bobject%20Object%5D&name=image-20220323161232522.png&originHeight=897&originWidth=1375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=384510&status=done&style=shadow&taskId=u0dd9e0a1-c1a1-4974-9f3f-edf90b9663e&title=&width=1375)
  • Access Token 的有效期比较短,当Acesss Token由于过期而失效时,使用Refresh Token就可以获取到新的Token,如果Refresh Token也失效了,用户就只能重新登录了。
  • Refresh Token 及过期时间是存储在服务器的数据库中,只有在申请新的 Acesss Token 时才会验证,不会对业务接口响应时间造成影响,也不需要向 Session 一样一直保持在内存中以应对大量的请求 。

Token 和 Session 的区别

  • Session 是一种记录服务器和客户端会话状态的机制,使服务端有状态化,可以记录会话信息。而 Token 是令牌访问资源接口(API)时所需要的资源凭证。Token 使服务端无状态化,不会存储会话信息
  • Session 和 Token 并不矛盾,作为身份认证 Token 安全性比 Session 好,因为每一个请求都有签名还能防止监听以及重放攻击,而 Session 就必须依赖链路层来保障通讯安全了。如果你需要实现有状态的会话,仍然可以增加 Session 来在服务器端保存一些状态。
  • 所谓 Session 认证只是简单的把 User 信息存储到 Session 里,因为 SessionID 的不可预测性,暂且认为是安全的。
    而 Token ,如果指的是 OAuth Token 或类似的机制的话,提供的是 认证授权 ,认证是针对用户,授权是针对 App 。其目的是让某 App 有权利访问某用户的信息。这里的 Token 是唯一的。不可以转移到其它 App上,也不可以转到其它用户上。Session 只提供一种简单的认证,即只要有此 SessionID ,即认为有此 User 的全部权利。是需要严格保密的,这个数据应该只保存在站方,不应该共享给其它网站或者第三方 App。
    简单来说:如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token 。如果永远只是自己的网站,自己的 App,用什么就无所谓了。

JWT

JWT是JSON Web Token的缩写。传统的基于Cookie-Session的认证机制对于单机没有问题,但对于**服务器集群**或者**跨域**的服务导向架构就要求session数据共享,每台服务器都能读取session。对该问题的一种解决方案可以采用**session数据持久化**,写入数据库或者其他持久层,各种服务收到请求后,都向持久层请求数据,这种方法的优点是架构清晰,缺点是工程量大,另外持久层出现问题,单点请求会失败。

另外一种方案是将所有数据保存在客户端,每次请求时都将数据发送回服务器,JWT就是这种方案的代表。**JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户。以后,用户与服务端通信的时候,都要发回这个 JSON 对象**。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。**服务器就不保存任何 session 数据了,也就是说,服务器变成**`**无状态**`**了,从而比较容易实现扩展**。

JWT 认证流程:

  • 用户输入用户名/密码登录,服务端认证成功后,会返回给客户端一个 JWT
  • 客户端将 token 保存到本地(通常使用 localstorage,也可以使用 cookie)
  • 当用户希望访问一个受保护的路由或者资源的时候,需要请求头的 Authorization 字段中使用Bearer 模式添加 JWT,其内容看起来是这样:Authorization: Bearer <token>

JWT的结构:是一个没有换行的字符串,该字符串用点(**.**)分隔成三部分。
image-20220323144413520.png
JWT的三部分依次如下:

  • Header(头部):Header 部分是一个 JSON 对象,如下所示,描述 JWT 的元数据。最后会将该JSON 对象使用 Base64URL 算法转成字符串。
    {
    "alg": "HS256",  // alg属性表示签名的算法, 默认是: HS256
    "typ": "JWT"     // typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT
    }
    
  • Payload(负载):Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

    iss (issuer):签发人
    exp (expiration time):过期时间
    sub (subject):主题
    aud (audience):受众
    nbf (Not Before):生效时间
    iat (Issued At):签发时间
    jti (JWT ID):编号
    

    除了官方字段,还可以在这个部分定义私有字段。JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。这个 JSON 对象也要使用 Base64URL 算法转成字符串。

  • Signature(签名):Signature 部分是对前两部分的签名,防止数据篡改。首先需要指定一个密钥(secret),这个密钥只有服务器才知道,不能泄露给用户。然后使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名

    HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
    


    算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。

JWT 的几个特点
(1)JWT 默认是不加密(该情况不能将秘密数据写入 JWT),但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
(2)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
(3)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
(4)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
(5)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
(6)因为 JWT 并不使用 Cookie 的,所以可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS)
(7)因为用户的状态不再存储在服务端的内存中,所以这是一种无状态的认证机制。

Token 和 JWT 的相同点与区别
相同:

  • 都是访问资源的令牌
  • 都可以记录用户的信息
  • 都是使服务端无状态化
  • 都是只有验证成功后,客户端才能访问服务端上受保护的资源

区别:

  • Token:服务端验证客户端发送过来的 Token 时,还需要查询数据库获取用户信息,然后验证 Token 是否有效。
  • JWT: 将 Token 和 Payload 加密后存储于客户端,服务端只需要使用密钥解密进行校验(校验也是 JWT 自己实现的)即可,不需要查询或者减少查询数据库,因为 JWT 自包含了用户信息和加密的数据。

使用 token 时需要考虑的问题

  • 如果你认为用数据库来存储 token 会导致查询时间太长,可以选择放在内存当中。比如 redis 很适合你对 token 查询的需求。
  • token 完全由应用管理,所以它可以避开同源策略
  • token 可以避免 CSRF 攻击(因为不需要 cookie 了)
  • 移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token

使用 JWT 时需要考虑的问题

  • 因为 JWT 并不依赖 Cookie 的,所以你可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS)
  • JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
  • JWT 不加密的情况下,不能将秘密数据写入 JWT。
  • JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
  • JWT 最大的优势是服务器不再需要存储 Session,使得服务器认证鉴权业务可以方便扩展。但这也是 JWT 最大的缺点:由于服务器不需要存储 Session 状态,因此使用过程中无法废弃某个 Token 或者更改 Token 的权限。也就是说一旦 JWT 签发了,到期之前就会始终有效,除非服务器部署额外的逻辑。
  • JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
  • JWT 适合一次性的命令认证,颁发一个有效期极短的 JWT,即使暴露了危险也很小,由于每次操作都会生成新的 JWT,因此也没必要保存 JWT,真正实现无状态。
  • 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

HTTP的优缺点

  • 优点:
    • 简单:报文格式就是headr+body,头部信息也是key:value形式,易于理解和学习;
    • HTTP协议里的各类请求方法、URI/URL、状态码、头字段等每个组成要求都没有被固定死,都允许开发人员自定义和扩充
    • HTTP 的应用范围非常的广泛,同时天然具有跨平台的优越性。
  • 缺点:
    • 无状态:服务器不会去记忆 HTTP 的状态,所以不需要额外的资源来记录状态信息,这能减轻服务器的负担,能够把更多的 CPU 和内存用来对外提供服务。无状态的坏处:它在完成有关联性的操作时会非常麻烦。无状态的解决方案: cookie和session。
    • 不安全:使用明文通信(不加密),通信内容会被窃取;不验证通信方身份,有可能遭遇伪装; 无法证明报文的完整性,所以报文可能遭到篡改。
    • 队头阻塞:当 http 开启长连接时,共用一个 TCP 连接,同一时刻只能处理一个请求,那么当前请求耗时过长的情况下,其它的请求只能处于阻塞状态,也就是著名的队头阻塞问题。

HTTP性能[💖]

HTTP 协议是基于 TCP/IP,并且使用了「请求 - 应答」的通信模式,所以性能的关键就在这两点里。有以下方法用于提升性能:

  • 长连接
    HTTP/1.0 性能上的一个很大的问题,那就是每发起一个请求,都要新建一次 TCP 连接(三次握手),而且是串行请求,做了无谓的 TCP 连接建立和断开,增加了通信开销。
    HTTP/1.1 提出了长连接的通信方式,也叫持久连接。这种方式的好处在于减少了 TCP 连接的重复建立和断开所造成的额外开销,减轻了服务器端的负载。持久连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。

image-20220321164655675.png

  • 管道网络传输

    HTTP/1.1 采用了长连接的方式,可以使用管道传输:在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。
    image-20220321165619801.png

  • 队头阻塞

    假设客户端需要请求两个资源。以前的做法是,在同一个TCP连接里面,先发送 A 请求,然后等待服务器做出回应,收到后再发出 B 请求。管道机制则是允许浏览器同时发出 A 请求和 B 请求。但是服务器还是按照FIFO顺序,先回应 A 请求,完成后再回应 B 请求。要是前面的回应特别慢,后面就会有许多请求排队等着。这称为「队头堵塞」。
    image-20220321165552423.png

HTTP的keep-alive是干什么的?[💖]

在早期的HTTP/1.0中,每次http请求都要创建一个连接,而创建连接的过程需要消耗资源和时间,为了减少资源消耗,缩短响应时间,就需要重用连接。在后来的HTTP/1.1中,引入了重用连接的机制,就是在http请求头中加入Connection: keep-alive来告诉对方这个请求响应完成后不要关闭,下一次还用这个请求继续交流。协议规定HTTP/1.0如果想要保持长连接,需要在请求头中加上Connection: keep-alive
keep-alive的优点:

  • 较少的CPU和内存的使用(由于同时打开的连接的减少了)
  • 允许请求和应答的HTTP管线化
  • 降低拥塞控制 (TCP连接减少了)
  • 减少了后续请求的延迟(无需再进行握手)
  • 报告错误无需关闭TCP连

常见问题

浏览器输入url按回车背后经历了哪些过程?[💖]

参考资料:https://juejin.cn/post/6994066112203718686#heading-1

1、 输入
在输入的过程中,浏览器的UI线程会实时捕捉输入的内容,如果输入的不是网址或者协议不合法的话,就会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的URL,准备进行搜索。然后下一步浏览器进程会通过IPC把URL发送给网络进程,然后网络进程要先查找本地缓存
2、检查缓存

  • 如果有缓存,并且没有过期,就不发送请求,直接拿来解码再开始渲染流程(后面的步骤);
    • 如果是https的话,有可能先找Service Worker,比如你设置了请求拦截、离线缓存的话
    • 如果没有,再找浏览器的内存缓存Memory Cache)
    • 如果还没有,再找硬盘缓存Disk Cache)( 强缓存和协商缓存都属于硬盘缓存)
  • 如果没有缓存或者缓存过期,再开始解析URL,解析出要请求的服务器 的IP地址;

3、URL解析
将请求需要的协议域名端口路径这些信息解析提取出来。然后根据解析出来的域名,进行DNS解析,找到要请求的服务器的IP地址。

4、 DNS解析过程
DNS(Domain Name System)是域名“系统”的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,它用于TCP/IP网络,它所提供的服务是用来将主机名和域名转换为IP地址的工作。DNS服务器有三种类型:根 DNS 服务器、顶级域(Top-Level Domain, TLD)DNS 服务器和权威 DNS 服务器。
image-20220323192547346.png
首先在客户端进行查询有没有解析过的记录,也就是DNS缓存,这个查询是递归查询。在这里任何一步找到就会结束查找流程(整个过程客户端只发出一次查询请求)
image-20220323190945549.png
如果都没有找到,就会走DNS服务器设置的转发器请求,如果没设置转发模式,就向13根(全球共有13个根域服务器的IP地址)发起解析请求,这里的查询方式是迭代查询,如下图:
image-20220323191143420.png

**DNS使用的是TCP协议还是UDP协议**?【💖💖】<br />    DNS占用53号端口,同时使用TCP和UDP协议。 DNS的规范规定了2种类型的DNS服务器,一个叫主DNS服务器,一个叫辅助DNS服务器。在一个区中主DNS服务器从自己本机的数据文件中读取该区的DNS数据信息,而辅助DNS服务器则从区的主DNS服务器中读取该区的DNS数据信息。当一个辅助DNS服务器启动时,它需要与主DNS服务器通信,并加载数据信息,这就叫做**区域传送**(zone transfer)。<br />    TCP与UDP传送字节的长度限制:UDP报文的最大长度为512字节,而TCP则允许报文长度超过512字节。当DNS查询超过512字节时,协议的TC标志出现删除标志,这时则使用TCP发送。通常UDP报文一般不会大于512字节。

区域传输时使用TCP协议:

  • 辅域名服务器会定时(一般3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,会执行一次区域传送,进行数据同步。区域传送使用TCP而不是UDP,因为数据同步传送的数据量比一个请求应答的数据量要多得多。
  • TCP是一种可靠连接,保证了数据的准确性。

域名解析时使用UDP协议:
客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过三次握手,这样DNS服务器负载更低,响应更快。理论上客户端也可以指定向DNS服务器查询时用TCP,但事实上很多DNS服务器进行配置的时候,仅支持UDP查询包。

5、 建立TCP连接
经过前面一步拿到了目的主机的IP地址后,开始正式发起请求,经过三次握手建立TCP连接:
第一次握手:客户端发送一个带 SYN=1,seq=X 的数据包到服务器端口。
第二次握手:服务器发回一个带 SYN=1, ACK=X+1, seq=Y 的响应包以示传达确认信息。
第三次握手:客户端再回传一个带 ACK=Y+1, seq=x+1 的数据包,代表”握手结束” 。
如果用的是http,这时连接成功进入传输阶段,如果是https,这时候还需要进行一个TLS加密协议的握手过程。

6、建立连接之后
连接建立成功之后,浏览器会构建请求行、cookie等数据附加到请求头中,发给服务器,服务器接受请求并解析。如果没有对应的资源就404了。否则检查HTTP请求头有没有包含协商缓存信息(前面查询强缓存已过期的话会走这个步骤),如果验证缓存没有更新,过期的缓存依然可以使用,就返回304和空响应体

  1. 要是没有缓存或者资源更新了,还没有CDN的话,就读取完整请求并准备http响应,进行查询数据库等操作
  2. 要是连接的是CDN节点,并且正好有这个资源就直接返回,要是没有资源或者资源更新了的话,CDN服务器就去源站获取文件,如果源站也没有,就404了,有的话就返回给CDN节点缓存起来

然后将响应数据通过之前建立的TCP连接,返回给浏览器的网络进程。浏览器接收到响应数据之后,如果是http1.1以下则直接关闭连接,否则双方都可以根据情况选择关闭TCP连接或者保留重用,现在浏览器默认都会保持连接(keep-alive)

6、四次挥手关闭连接
过程:略。

7、解析响应数据
如果返回的状态码是301302就需要重定向到其他URL,在重定向地址会在响应头的Location字段中,然后一切从头开始,否则然后根据情况选择关闭TCP连接或者保留重用。然后网络线程会通过SafeBrowsing来检查站点是不是恶意站点,如果是就展示警告页面,告诉你这个站点有安全问题,浏览器会阻止访问,当然也可以强行继续访问。
响应成功返回状态码2xx,然后判断资源能不能缓存,如果可以就先缓存起来。然后对响应解码,比如gzip压缩,然后根据资源类型(Content-Type)决定如何处理,如果浏览器判断是下载文件,那么请求会被提交给浏览器的下载管理器,同时URL请求流程就结束了。
否则网络线程会通知UI线程,然后UI线程会创建一个渲染器进程来准备渲染页面。然后浏览器进程通过IPC管道将数据传给渲染器进程的主线程,准备渲染流程。如果从A页面里面打开一个新的页面B页面,而A页面和B页面又属于同一站点的话,A和B就共用一个渲染进程,其他情况就为B创建一个新的渲染进程。

8、 浏览器解析渲染页面。
浏览器解析渲染页面分为一下五个步骤:

  • 根据 HTML 解析出 DOM
  • 根据 CSS 解析生成 CSS 规则树
  • CSS 规则树附着到 DOM 树上 ,构造生成渲染(Render)树
  • 根据渲染树计算每一个节点的信息
  • 根据计算好的信息绘制页面

image-20220323163921361.png

Http 1.x, 2.0, 3.0的区别[💖]

参考资料:
https://halfrost.com/http/#toc-18
https://www.cnblogs.com/rickiyang/p/13138574.html
https://github.com/funnycoderstar/blog/issues/127
https://www.cnblogs.com/xiaolincoding/p/12442435.html
https://segmentfault.com/a/1190000006879700

HTTP / 1.0 和HTTP / 1.1 的一些区别:

  • 缓存处理:在HTTP / 1.0 中,使用header里的 If-Modified-Since, Expires 来做为缓存判断的标准;HTTP / 1.1 则引入了更多的缓存控制策略例如 Entity tagIf-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略;
  • 带宽优化以及网络连接的使用:HTTP / 1.0 中存在一些浪费的现象,例如客户端只是需要某个对象一部分,而服务器却将整个对象送过来了,并且不支持断点续传的功能;HTTP / 1.1则在请求头中引入了 range 头域,它允许只请求资源的某一个部分,即返回 206。 这样开发者可以自由选择以便充分利用带宽和链接;
  • 错误通知的管理:在 HTTP/1.1 中新增了24个错误状态码;
  • Host 头处理,在 HTTP/ 1.0 中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机,并且它们共享一个IP地址。HTTP/ 1.1 的请求消息和响应消息都应支持 Host 头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request);
  • 长连接:HTTP / 1.1 支持长连接(Persistent Connection)和请求的流水线(Pipelining)处理,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟,在 HTTP/ 1.1 中默认开启Connection: keep-alive,一定程度上弥补了 HTTP/1.0 每次请求都要创建连接的缺点。

为什么 HTTP1.1 不能实现多路复用?
HTTP/1.1 不是二进制传输,而是通过文本进行传输。由于没有流的概念,在使用并行传输(多路复用)传递数据时,接收端在接收到响应后,并不能区分多个响应分别对应的请求,所以无法将多个响应的结果重新进行组装,也就实现不了多路复用。

多路复用与keep alive的区别[💖]
HTTP1.X虽然可以采用Keep alive来解决复用TCP的问题,但是还是无法解决请求阻塞。产生这个问题的原因在于HTTP1.x需要每条请求都是可以识别,按顺序发送,否则serve就无法判断该响应哪个具体的请求。
而HTTP2采用多路复用是指,在同一个域名下,开启一个TCP的connection,每个请求以stream的方式传输,每个stream有唯一的标识,connection一旦建立,后续的请求都可以复用这个connection并且可以同时发送,server端可以根据stream的唯一标识来响应对应的请求。

HTTP / 2.0 与 HTTP / 1.1 有几处基本的不同

  • 头部压缩
    HTTP/1.1 的首部带有大量信息,而且每次都要重复发送。HTTP/2.0 要求通讯双方各自缓存一份首部字段表,从而避免了重复传输。
    HTTP/2.0 会压缩头,如果同时发出多个请求,他们的头是一样的或是相似的,那么协议会消除重复的部分
    这就是所谓的 HPACK 算法:在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。

  • 二进制分帧
    HTTP/2.0 不再像 HTTP/1.1 里的纯文本形式的报文,而是全面采用了二进制格式,头信息和数据体都是二进制,并且统称为帧(frame):头信息帧和数据帧。二进制协议解析起来更高效。

  • 数据流
    每个请求或回应的所有数据包,称为一个数据流(Stream)。每个数据流都标记着一个独一无二的编号,其中规定客户端发出的数据流编号为奇数, 服务器发出的数据流编号为偶数。
    客户端还可以指定数据流的优先级。优先级高的请求,服务器就先响应该请求
  • 多路复用
    HTTP/2 是可以在一个TCP连接中并发多个请求或回应,而不用按照顺序一一对应
    移除了 HTTP/1.1 中的串行请求,不需要排队等待,也就不会再出现「队头阻塞」问题,降低了延迟,大幅度提高了连接的利用率
  • 服务端推送
    HTTP/2 还在一定程度上改善了传统的「请求 - 应答」工作模式,服务不再是被动地响应,也可以主动向客户端发送消息。
    举例来说,在浏览器刚请求 HTML 的时候,就提前把可能会用到的 JS、CSS 文件等静态资源主动发给客户端,减少延时的等待,也就是服务器推送(Server Push,也叫 Cache Push)。

image-20220321185342114.png

参考资料
HTTP协议:
http://xiaobaidebug.top/2021/02/25/图解网络/硬核!漫画图解HTTP知识点+面试题/#more

HTTPS

参考资料:https://www.cnblogs.com/rickiyang/p/13218088.html

HTTP和HTTPS的区别

  1. HTTP 本身不具备加密的功能,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。
  2. HTTP协议无法证明通信的报文完整性,因此,在请求或响应送出之后直到对方接收之前的这段时间内,即使请求或响应的内容遭到篡改,也没有办法获悉。
  3. HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,此外数据还需要加密运算,才可进入加密报文传输。所以HTTPS比HTTP慢。
  4. HTTP 的端口号是 80,HTTPS 的端口号是 443。
  5. HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。

安全通信

如果通信过程具备以下四个特性,就可以认为是 安全 的:

  • 机密性(Secrecy/Confidentiality):是指对数据的保密,只能由可信的人访问,对其他人是不可见的秘密,简单来说就是不能让不相关的人看到不该看的东西。
  • 完整性(Integrity,也叫一致性):是指数据在传输过程中没有被窜改,不多也不少,完完整整地保持着原状。
  • 身份认证(Authentication):是指确认对方的真实身份,也就是 证明你真的是你,保证消息只能发送给可信的人。
    如果通信时另一方是假冒的网站,那么数据再保密也没有用,黑客完全可以使用冒充的身份 套 出各种信息,加密和没加密一样。
  • 不可否认(Non-repudiation/Undeniable),也叫不可抵赖,意思是不能否认已经发生过的行为,不能说话不算数,耍赖皮

HTTPS通过什么保证是安全的?

HTTP + 加密 + 认证 + 完整性保护 = HTTPS。<br />    HTTPS 协议的主要功能基本都依赖于 TLS/SSL 协议,TLS/SSL 的功能实现主要依赖于三类基本算法:对称加密、非对称加密、散列函数 。利用非对称加密实现身份认证和密钥协商,对称加密算法采用协商的密钥对数据加密,基于散列函数验证信息的完整性。

加密

  • 对称加密(共享密钥加密):加密和解密使用同一个密钥,常见的对称加密算法:DES,AES 等
    • 存在问题:如何安全的发送密钥?发送密钥就有被窃取的风险,但不发送,对方就不能解密。
  • 非对称加密(公开密钥加密):使用一对非对称的密钥。一把叫做私有密钥,另一把叫公开密钥。公钥加密的信息,只有私钥才能解密。反之,私钥加密的信息,只有公钥才能解密。 常见的非对称加密算法:RSA,ECC 等。
    • 可以解决共享密钥加密的问题(密钥交换会被窃取):发送密文的一方使用对方的公开密钥进行加密处理,对方收到被加密的信息后,再使用自己的私有密钥进行解密。利用这种方式,不需要发送用来解密私有密钥,也不必担心密钥被攻击者窃听而盗走。在 非对称加密 算法中,需要应用到复杂的数学运算,虽然保证了安全,但速度很慢,比对称加密算法差了好几个数量级。
  • 混合加密机制
    • TLS 里使用了 “混合加密” 的方式:在通信刚开始的时候使用 非对称加密 算法解决密钥交换的问题,然后用随机数产生对称算法使用的 会话密钥(session key),会话密钥很短(16B或32B),再用公钥加密。对方拿到密文后用私钥解密,取出会话密钥。这样,双方就实现了对称密钥的安全交换,后续就不再使用非对称加密,全都使用对称加密。

认证
使用数字证书认证机构(Certificate Authority,CA)颁发的公开密钥证书,证明公开密钥正确性。

  • 数字证书一般有两个作用:
    • 服务器向浏览器证明自己的身份,毕竟秘钥、甚至服务器域名都是可以伪造的。
    • 把公钥传给浏览器。证书本身是由权威、受信任的证书颁发机构 (CA) 授予的。

完整性

  • 摘要算法(Digest Algorithm)
    摘要算法也就是常说的哈希函数,它能够把任意长度的数据压缩成固定长度、而且独一无二的摘要字符串。摘要算法是单向加密算法,它只有算法,没有密钥,加密后的数据无法解密,不能从摘要逆推出原文。摘要算法保证了 数字摘要 和原文是完全等价的。所以只要在原文后附上它的摘要,就能够保证数据的完整性。
    例如A发送一条信息再加上一条摘要到B,B收到后也计算一下消息的摘要,把这两份摘要要做个对比,如果一致,就说明消息是完整可信的,没有被修改。
  • 数字签名
    加密算法结合摘要算法,通信过程可以说是比较安全了。但这里还有漏洞,就是通信的两个端点(endpoint)。黑客可以伪装成网站来窃取信息,也可以伪装成用户,向网站发送支付、转账等消息,网站没有办法确认用户的身份,钱可能就这么被偷走了。

    使用私钥再加上摘要算法,就能够实现 数字签名,同时实现 身份认证不可否认。数字签名的原理其实很简单,就是把公钥私钥的 用法反过来,之前是公钥加密、私钥解密,现在是私钥加密、公钥解密。但又因为非对称加密效率太低,所以私钥只加密原文的摘 要,这样运算量就小的多,而且得到的数字签名也很小,方便保管和传输。

SSL / TLS 握手详细过程

参考资料:https://halfrost.com/https_tls1-2_handshake/#toc-0
SSL 安全套接层(Secure Sockets Layer) TLS 传输层安全协议(Transport Layer Security)
TLS协议的组成:

  • 记录协议(Record Protocol)规定了 TLS 收发数据的基本单位:记录(record)。它有点像是 TCP 里的 segment,所有的其他子协议都需要通过记录协议发出。但多个记录数据可以在一个 TCP 包里一次性发出,也并不需要像 TCP 那样返回 ACK。
  • 警报协议(Alert Protocol)的职责是向对方发出警报信息,有点像是 HTTP 协议里的状态码。比如,protocol_version 就是不支持旧版本,bad_certificate 就是证书有问题,收到警报后另一方可以选择继续,也可以立即终止连接。
  • 握手协议(Handshake Protocol)是 TLS 里最复杂的子协议,要比 TCP 的 SYN/ACK 复杂的多,浏览器和服务器会在握手过程中协商 TLS 版本号、随机数、密码套件等信息,然后交换证书和密钥参数,最终双方协商得到会话密钥,用于后续的混合加密系统。
  • 变更密码规范协议(Change Cipher Spec Protocol),它非常简单,就是一个通知,告诉对方,后续的数据都将使用加密保护。那么反过来,在它之前,数据都是明文的。

TLS 的握手过程如下

  1. “client hello”消息:客户端通过发送”client hello”消息向服务器发起握手请求,该消息包含了客户端所支持的 TLS 版本和密码组合以供服务器进行选择,还有一个”client random”随机字符串。
  2. “server hello”消息:服务器发送”server hello”消息对客户端进行回应,该消息包含了数字证书,服务器选择的密码组合和”server random”随机字符串。
  3. 验证:客户端对服务器发来的证书进行验证,确保对方的合法身份,验证过程可以细化为以下几个步骤:
    1. 检查数字签名
    2. 验证证书链 (这个概念下面会进行说明)
    3. 检查证书的有效期
    4. 检查证书的撤回状态 (撤回代表证书已失效)
  4. “premaster secret”字符串:客户端向服务器发送另一个随机字符串”premaster secret (预主密钥)”,这个字符串是经过服务器的公钥加密过的,只有对应的私钥才能解密。
  5. 使用私钥:服务器使用私钥解密”premaster secret”。
  6. 生成共享密钥:客户端和服务器均使用 client random,server random 和 premaster secret,并通过相同的算法生成相同的共享密钥 KEY
  7. 客户端就绪:客户端发送经过共享密钥 KEY加密过的”finished”信号。
  8. 服务器就绪:服务器发送经过共享密钥 KEY加密过的”finished”信号。
  9. 达成安全通信:握手完成,双方使用对称加密进行安全通信。

1607781-20200701110940594-1175226763.jpg