应用层

726 阅读56分钟

应用层常见协议:

  • 超文本传输

    HTTP、HTTPS

  • 文件传输

    FTP

  • 电子邮件

    SMTP、POP3、IMAP

  • 动态主机配置

    DHCP

  • 域名系统

    DNS

域名(Domin Name)

由于IP地址不方便记忆,并且不能表达组织的名称和性质,人们设计出了域名(比如baidu.com)

但实际上,为了能够访问到具体的主机,最终还是得知道目标主机的IP地址。

使用IP地址只占用4字节,可以减少路由器的负担,节省流量

根据级别不同,域名可以分为

  • 顶级域名(Top-level Domain,简称TLD)
  • 二级域名
  • 三级域名
  • 。。。

顶级域名

  • 通用顶级域名(General Top-level Domain,简称gTLD)

    .com(公司)
    .net(网络机构)
    .org(组织结构)   
    .edu(教育)   
    .gov(政府部门)   
    .int(国际组织)
    
  • 国家及地区顶级域名(Country Code Top-level Domain,简称ccTLD)

    .cn(中国)
    .jp(日本)
    .uk(英国)
    
  • 新通用顶级域名(New Generic Top-level Domain,简称:New gTLD)

    .vip
    .xyz
    .top
    .club
    .shop
    

二级域名

二级域名是指顶级域名之下的域名

在通用顶级域名下,它一般指域名注册人的名称,例如google、baidu、microsoft等

在国家及地区顶级域名下,它一般指注册类别的,例如com、edu、gov、net等

DNS

DNS的全程是:Domain Name System,译为:域名系统

利用DNS协议,可以将域名(比如baidu.com)解析成对应的IP地址(比如220.181.38.148)

DNS可以基于UDP协议,也可以基于TCP协议,服务器占用53端口

客户端首先会访问最近的一台DNS服务器(也就是客户端自己配置的DNS服务器)

所有的DNS服务器都记录了DNS根域名服务器的IP地址

上级DNS服务器记录了下一级DNS服务器的IP地址

全球一共13台IPv4的DNS根域名服务器、25台IPv6的DNS根域名服务器

DHCP

DHCP(Dynamic Host Configuration Protocol),译为:动态主机配置协议

DHCP协议基于UDP协议,客户端是68端口,服务器是67端口

DHCP服务器会从IP地址池中,挑选一个IP地址出租给客户端一段时间,时间到期就回收他们

平时家里上网的路由器就可以充当DHCP服务器

分配IP地址的4个阶段

  • DISCOVER:发现服务器

    发广播包(源IP是0.0.0.0,目标IP是255.255.255.255,目标MAC地址是FF:FF:FF:FF:FF:FF)

  • OFFER:提供租约

    服务器返回可以租用的IP地址,以及租用期限、子网掩码、网关、DNS等信息

    注意:这里可能会有多个服务器提供租约

  • REQUEST:选择IP地址

    客户端选择一个OFFER,发送广播包进行回应

  • ACKNOWLEDGE:确认

    被选中的服务器发送ACK数据包给客户端

    至此,IP地址分配完毕

注意:

  • DHCP服务器可以跨网段分配IP地址吗?(DHCP服务器、客户端不在同一个网段)

    可以借助DHCP中继代理(DHCP Relay Agent)实现跨网段分配IP地址

  • 自动续约

    客户端会在租期不足的时候,自动向DHCP服务器发送REQUEST信息申请续约

  • 常用命令

    • ipconfig /all 可以看到DHCP相关的详细信息,比如租约过期时间、DHCP服务器地址等
    • ipconfig /release 释放租约
    • ipconfig /renew 重新申请IP地址,申请续约(延长租期)

telnet

可以直接面向HTTP报文与服务器交互

可以更清晰直观的看到请求报文、响应报文的内容

可以校验请求报文格式的正确与否

➜  ~ telnet localhost 80

Trying ::1...
Connected to localhost.
Escape character is '^]'.
GET /index.html HTTP/1.1
Host: localhost:80

HTTP/1.1 200 OK
Date: Wed, 09 Dec 2020 14:05:32 GMT
Server: Apache/2.4.41 (Unix)
Last-Modified: Tue, 08 Dec 2020 14:55:46 GMT
ETag: "143-5b5f523376080"
Accept-Ranges: bytes
Content-Length: 323
Content-Type: text/html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>

    <p>hello world!!!</p>

    <button onclick="buttonClick()">button</button>
    
</body>

<script>
    function buttonClick() {
        window.location.href = "https://www.baidu.com";
    }
</script>


</html>Connection closed by foreign host.

HTTP

HTTP(HyperText Transfer Protocol)超文本传输协议

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

  • 互联网中应用最广泛的应用层协议之一

  • 设计最初目的是:提供一种发布和接收HTML页面的方法,由URI来标识具体的资源

  1. HTTP 是灵活可扩展的,可以任意添加头字段实现任意功能;
  2. HTTP 是可靠传输协议,基于 TCP/IP 协议“尽量”保证数据的送达;
  3. HTTP 是应用层协议,比 FTP、SSH 等更通用功能更多,能够传输任意数据;
  4. HTTP 使用了请求 - 应答模式,客户端主动发起请求,服务器被动回复请求;
  5. HTTP 是无状态的,可以轻松实现集群化,扩展性能,但有时也需要用 Cookie 技术来实现“有状态”;
  6. HTTP 是明文传输,数据完全肉眼可见,能够方便地研究分析,但也容易被窃听;
  7. HTTP 是不安全的,无法验证通信双方的身份,也不能判断报文是否被窜改;
  8. HTTP 的性能不算差,但不完全适应现在的互联网,还有很大的提升空间。

发展历史

  • 1991年,HTTP/0.9

    只支持GET请求方法获取文本数据(比如HTML文档),且不支持请求头,响应头等,无法向服务器传递太多信息

  • 1996年,HTTP/1.0

    支持POST HEAD等请求方法,支持请求头、响应头等,支持更多种数据类型(不再局限于文本数据)

    浏览器的每次请求都需要与服务器建立一个TCP连接,请求处理完成后立即断开TCP连接

  • 1997年,HTTP/1.1 (最经典,使用最广泛的版本)

    支持PUT DELETE等请求方法

    采用持久连接(Connection:keep-alive),多个请求可以共用同一个TCP连接

  • 2015年,HTTP/2.0

    HTTP/2 基于Google的SPDY协议,注重性能改善,但还未普及

  • 2018年,HTTP/3.0

    HTTP/3 基于Google的QUIC协议,是将来的发展方向

报文格式

GET / HTTP/1.1
Host: localhost
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
If-None-Match: "143-5b5f523376080"
If-Modified-Since: Tue, 08 Dec 2020 14:55:46 GMT

实体主体,若是POST,则此部分放参数(username=123&pwd=456)

HTTP/1.1 200 OK
Date: Tue, 08 Dec 2020 15:01:40 GMT
Server: Apache/2.4.41 (Unix)
Last-Modified: Tue, 08 Dec 2020 14:55:46 GMT
ETag: "143-5b5f523376080"
Accept-Ranges: bytes
Content-Length: 323
Content-Type: text/html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>

    <p>hello world!!!</p>

    <button onclick="buttonClick()">button</button>
    
</body>

<script>
    function buttonClick() {
        window.location.href = "https://www.baidu.com";
    }
</script>


</html>

ABNF

ABNF(Augmented BNF)是BNF 的修改增强版

RFC 5234 中表明:ABNF用作internet中通信协议的定义语言

ABNF是最严谨的HTTP报文格式描述形式,脱离ABNF谈论HTTP报文格式,往往都是片面 不严谨的

HTTP报文格式的定义

1. 报文格式 - 整体

HTTP-message = start-line
							 *(header-field CRLF)
							 CRLF
							 [ message-body ]
/任选一个
*0个或多个。 2*表示至少2个3*6表示3到6个
()组成一个整体
[]可选(可有可无)
  • start-line

    表示 request-line / status-line 中的任意一个

  • *(header-field CRLF)

    表示首部行,可以没有,可以有多个,key:value形式表示,每一行结尾有回车换行

  • CRLF

    表示回车换行

  • [ message-body ]

    可选项。如果是post请求,请求参数放在此 ( username=123&pwd=456 )

2. request-line / status-line

  1. request-line:

    request-line = method SP request-target SP HTTP-version CRLF
    
    HTTP-version = HTTP-name"/"DIGIT"."DIGIT
    
    HTTP-name = %x48.54.54.50   ;HTTP
    

    对应的请求行为

    GET /hello/ HTTP/1.1
    
  2. status-line:

    status-line = HTTP-version SP status-code SP reason-phrase CRLF
    
    status-code = 3DIGIT    ;表示3位数字
    
    reason-phrase = *(HTAB / SP / VCHAR / obs-text)
    

    代表的响应行为

    HTTP/1.1 200 
    HTTP/1.1 200 OK
    

3. header-field

header-field = field-name":" OWS field-value OWS

field-name = token

field-value = *(field-content / obs-fold)

OWS表示 *(SP / HTAB),可以为空格或者tab键

Host: localhost

4. message-body

message-body = *OCTET

username=123&pwd=456

请求方法

Request methods 中描述了8中请求方法

GET  HEAD  POST  PUT  DELETE  CONNECT  OPTIONS  TRACE

Patch method 中描述了PATCH方法

  • GET

    常用于读取的操作,请求参数直接拼接在URL的后面(浏览器对URL是有长度限制的)

  • HEAD

    请求得到与GET请求相同的响应,但是没有响应体

    在下载一个大文件前,可以先获取其大小,再决定是否要下载。以此节约带宽资源

  • POST

    常用于添加、修改、删除的操作,请求参数可以放到请求体中(没有大小限制)

  • PUT

    用于对已存在的资源进行整体覆盖

  • DELETE

    用于删除指定的资源

  • CONNECT

    可以开启一个客户端与所请求资源之间的双向沟通的通道,它可以用来创建隧道(tunnel)

    可以用来访问采用了SSL(HTTPS)协议的站点

  • OPTIONS

    用于获取目的资源所支持的通信选项,比如服务器支持的请求方式

    OPTIONS * HTTP/1.1
    
  • TRACE

    请求服务器回显其收到的请求信息,主要用于HTTP请求的测试或诊断

  • PATCH

    用于对资源进行部分修改(资源不存在,会创建新的资源)

URI

URI(Uniform Resource Identifier)统一资源标识符。包含了URL(Uniform Resource Locator)统一资源定位符和URN 两个部分

URI 本质上是一个字符串,这个字符串的作用是唯一地标记资源的位置或者名字

  • scheme

    方案名或者协议名,表示资源应该使用哪种协议来访问。最常用的就是http、https,此外还有不常用的有ftp、file、telent、mailto、rtsp等

  • 身份信息(user:passwd@)

    表示登录主机的用户名和密码。现在已经不推荐使用这种形式了,因为它把敏感信息以明文形式暴露出来,存在严重的安全隐患

  • 主机与端口(host:port)

    主机名可以是ip地址或者域名的形式,必须要有,否则浏览器就会找不到服务器。但端口号可以省略,浏览器等客户端会根据scheme使用默认的端口号,比如http端口号默认是80,https端口号默认是443

  • 路径(path)

    路径说明了资源位于服务器的什么地方。路径通常很像一个分级的文件系统路径,采用了UNIX的 / 风格。URI 的 path 部分必须以“/”开始,也就是必须包含“/”

  • 查询参数(query)

    在 path 之后,用一个“?”开始,但不包含“?”,表示对资源附加的额外要求。查询参数 query 有一套自己的格式,是多个“key=value”的字符串,这些 KV 值用字符“&”连接,浏览器和客户端都可以按照这个格式把长串的查询参数解析成可理解的字典或关联数组形式。

  • 片段标识符(#fragment)

    它是 URI 所定位的资源内部的一个“锚点”或者说是“标签”,浏览器可以在获取资源后直接跳转到它指示的位置。

    但片段标识符仅能由浏览器这样的客户端使用,服务器是看不到的。也就是说,浏览器永远不会把带“#fragment”的 URI 发送给服务器,服务器也永远不会用这种方式去处理资源的片段。

URI 的编码

刚才我们看到了,在 URI 里只能使用 ASCII 码,但如果要在 URI 里使用英语以外的汉语、日语等其他语言该怎么办呢?

还有,某些特殊的 URI,会在 path、query 里出现“@&?"等起界定符作用的字符,会导致 URI 解析错误,这时又该怎么办呢?

所以,URI 引入了编码机制,对于 ASCII 码以外的字符集和特殊字符做一个特殊的操作,把它们转换成与 URI 语义不冲突的形式。这在 RFC 规范里称为“escape”和“unescape”,俗称“转义”。

URI 转义的规则有点“简单粗暴”,直接把非 ASCII 码或特殊字符转换成十六进制字节值,然后前面再加上一个“%”。

例如,空格被转义成“%20”,“?”被转义成“%3F”。而中文、日文等则通常使用 UTF-8 编码后再转义,例如“银河”会被转义成“%E9%93%B6%E6%B2%B3”。

有了这个编码规则后,URI 就更加完美了,可以支持任意的字符集用任何语言来标记资源。

HTTP首部字段

通用首部字段(General Header Fields)

请求报文和响应报文两方都会使用的首部

  • Cache-Control

    控制缓存的行为。用来指定在这次的请求、响应链中的所有缓存机制都必须遵守的指令

    Cache-Control: max-age=5
    
    • max-age

      响应的最大age值,单位是秒。max-age=0通常与no-cache表达意思一样。都是想服务器获取最新资源

    • no-store

      不允许缓存,强制向源服务器再次验证。用于某些变化非常频繁的数据,例如秒杀页面;

    • no-cache

      它的字面含义容易与 no-store 搞混,实际的意思并不是不允许缓存,而是可以缓存,但在使用之前必须要去服务器验证是否过期,是否有最新的版本;

  • Connection

    该浏览器想要优先使用的连接类型,HTTP/1.1中的连接都会默认使用长连接

    Connection: keep-alive
    

    在客户端,可以在请求头里加上“Connection: close”字段,告诉服务器:“这次通信后就关闭连接”。服务器看到这个字段,就知道客户端要主动关闭连接,于是在响应报文里也加上这个字段,发送之后就调用 Socket API 关闭 TCP 连接。

    Connection: close
    
  • Date

    发送该消息的日期和时间

    Date: Wed, 09 Dec 2020 14:30:01 GMT
    
  • Pragma

    Pragma 是 HTTP/1.1 之前版本的历史遗留字段,仅作为与 HTTP/1.0的向后兼容而定义。

    Pragma: no-cache
    

    该首部字段属于通用首部字段,但只用在客户端发送的请求中。客户端会要求所有的中间服务器不返回缓存的资源。

    所有的中间服务器如果都能以 HTTP/1.1 为基准,那直接采用 Cache-Control: no-cache 指定缓存的处理方式是最为理想的。但要整体掌握全部中间服务器使用的 HTTP 协议版本却是不现实的。因此,发送的请求会同时含有下面两个首部字段。

    Cache-Control: no-cache
    Pragma: no-cache
    
  • Trailer

    首部字段 Trailer 会事先说明在报文主体后记录了哪些首部字段。该首部字段可应用在 HTTP/1.1 版本分块传输编码时。

  • Transfer-Encoding

    首部字段 Transfer-Encoding 规定了传输报文主体时采用的编码方式。 HTTP/1.1 的传输编码方式仅对 分块传输 编码有效。意思是报文里的 body 部分不是一次性发过来的,而是分成了许多的块(chunk)逐个发送。

    HTTP/1.1 200 OK
    Date: Tue, 03 Jul 2012 04:40:56 GMT 
    Cache-Control: public, max-age=604800 
    Content-Type: text/javascript; charset=utf-8 
    Expires: Tue, 10 Jul 2012 04:40:56 GMT  
    Content-Encoding: gzip
    ...
    Transfer-Encoding: chunked
    ...
    Connection: keep-alive
    
    cf0 ←16进制(10进制为3312) 
    ...3312字节分块数据...
    392 ←16进制(10进制为914) 
    ...914字节分块数据...
    0
    

    以上用例中,正如在首部字段 Transfer-Encoding 中指定的那样,有效 使用分块传输编码,且分别被分成 3312 字节和 914 字节大小的分块数据。

    1. 每个分块包含两个部分,长度头和数据块;
    2. 长度头是以 CRLF(回车换行,即\r\n)结尾的一行明文,用 16 进制数字表示长度;
    3. 数据块紧跟在长度头后,最后也用 CRLF 结尾,但数据不包含 CRLF;
    4. 最后用一个长度为 0 的块表示结束,即“0\r\n\r\n”。
  • Upgrade

    首部字段 Upgrade 用于检测 HTTP 协议及其他协议是否可使用更高的版本进行通信,其参数值可以用来指定一个完全不同的通信协议。

    Connection: Upgrade
    Upgrade: TLS/1.0
    
  • Via

    代理服务器的相关信息。每当报文经过一个代理节点,代理服务器就会把自身的信息追加到字段的末尾,就像是经手人盖了一个章。如果通信链路中有很多中间代理,就会在 Via 里形成一个链表,这样就可以知道报文究竟走过了多少个环节才到达了目的地。

    若客户端发送请求会经过proxy1和proxy2两个代理服务器,则Via就如下面所示

    Via: proxy1,proxy2
    
  • Warning

    错误通知

请求首部字段(Request Header Fields)

从客户端向服务端发送请求报文时使用的首部。补充了请求的附加内容、客户端信息、响应内容相关优先级等信息

  • Accept

    能够接受的响应内容类型(Content-Types)

    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
    

    ;q=0.9 代表权重值,用;进行分割。权重值q的范围为0-1(可精确到小数点后3位),且1为最大值。不指定权重q值时,默认为q=1.0

    • 文本文件
      text/html, text/plain, text/css ... 
      application/xhtml+xml, application/xml ...
    
    • 图片文件

      image/jpeg, image/gif, image/png ...
      
    • 视频文件

      video/mpeg, video/quicktime ...
      
    • 应用程序使用的二进制文件

      application/octet-stream, application/zip ...
      
  • Accept-Charset

    能够接受的字符集

    Accept-Charset: utf-8
    
  • Accept-Encoding

    能够接受的编码方式列表

    Accept-Encoding: gzip, deflate, br
    
    • gzip

      由文件压缩程序 gzip(GNU zip)生成的编码格式 (RFC1952),采用 Lempel-Ziv 算法(LZ77)及 32 位循环冗余 校验(Cyclic Redundancy Check,通称 CRC)。互联网上最流行的压缩格式

    • deflate

      组合使用 zlib 格式(RFC1950)及由 deflate 压缩算法 (RFC1951)生成的编码格式。流行程度仅次于gzip

    • br

      一种专门为HTTP优化的新压缩算法(Brotli)

    • compress

      由 UNIX 文件压缩程序 compress 生成的编码格式,采用 Lempel- Ziv-Welch 算法(LZW)。

    • identity

      不执行压缩或不会变化的默认编码格式

    采用权重 q 值来表示相对优先级,这点与首部字段 Accept 相同。另外,也可使用星号(*)作为通配符,指定任意的编码格式。

  • Accept-Language

    能够接受的响应内容的自然语言列表

    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
    
  • Authorization

    web认证信息。用来告知服务器,用户代理的认证信息(证 书值)。通常,想要通过服务器认证的用户代理会在接收到返回的 401 状态码响应后,把首部字段 Authorization 加入请求中

    Authorization: Basic dWVub3NlbjpwYXNzd29yZA==
    
  • Expect

    期待服务器的特定行为

  • From

    用户的电子邮箱地址

  • Host

    服务器的域名、端口号

    Host: www.baidu.com
    
  • User-Agent

    浏览器的身份标识字符串

    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36
    
  • Referer

    标识浏览器所访问的前一个页面,正是那个页面上的某个链接将浏览器带到了当前所请求的这个页面

    假设在我们本地的页面上点击一个按钮跳转到百度,此时在请求百度页面就会看到Referer字段

    Referer: http://localhost/
    
  • Range

    对于只需获取部分资源的范围请求,包含首部字段 Range 即可告知服 务器资源的指定范围。下面的示例表示请求获取从第 5001 字节至第 10000 字节的资源。

    接收到附带 Range 首部字段请求的服务器,会在处理请求之后返回状 态码为 206 Partial Content 的响应。无法处理该范围请求时,则会返 回状态码 200 OK 的响应及全部资源。

    Range: bytes=5001-10000
    
  • Origin

    发起一个针对跨域资源共享的请求

    Origin: https://www.baidu.com
    
  • Cookie

    之前由服务器通过Set-Cookie发送的Cookie

    Cookie: BIDUPSID=43F64A06F72D2A56E7FC0274274187DB;
    
  • If-Match

    形如 If-xxx 这种样式的请求首部字段,都可称为条件请求。服务器接收到附带条件的请求后,只有判断指定条件为真时,才会执行请求。

    If-Match: "123456"
    

    首部字段 If-Match,属附带条件之一,它会告知服务器匹配资源所用的实体标记(ETag)值。这时的服务器无法使用弱 ETag 值。

    服务器会比对 If-Match 的字段值和资源的 ETag 值,仅当两者一致时,才会执行请求。反之,则返回状态码 412 Precondition Failed 的响 应。

    还可以使用星号(*)指定 If-Match 的字段值。针对这种情况,服务器将会忽略 ETag 的值,只要资源存在就处理请求。

    If-Match: *
    
  • If-Modified-Since

    如果在 If-Modified-Since 字段指定的日期时间后,资源发生了 更新,服务器会接受请求

    If-Modified-Since: Thu, 15 Apr 2004 00:00:00 GMT
    

    首部字段 If-Modified-Since,属附带条件之一,它会告知服务器若 If-Modified-Since 字段值早于资源的更新时间,则希望能处理该请求。 而在指定 If-Modified-Since 字段值的日期时间之后,如果请求的资源都没有过更新,则返回状态码 304 Not Modified 的响应。

    If-Modified-Since 用于确认代理或客户端拥有的本地资源的有效性。 获取资源的更新日期时间,可通过确认首部字段 Last-Modified 来确定。

  • If-None-Match

    If-None-Match: "143-5b5f523376080"
    

    只有在 If-None-Match 的字段值与 ETag 值不一致时,可处理该请求。与 If-Match 首部字段的作用相反

    首部字段 If-None-Match 属于附带条件之一。它和首部字段 If-Match 作用相反。用于指定 If-None-Match 字段值的实体标记(ETag)值与请求资源的 ETag 不一致时,它就告知服务器处理该请求。

    在 GET 或 HEAD 方法中使用首部字段 If-None-Match 可获取最新的资源。因此,这与使用首部字段 If-Modified-Since 时有些类似。

  • If-Range

    首部字段 If-Range 属于附带条件之一。它告知服务器若指定的 If-Range 字段值(ETag 值或者时间)和请求资源的 ETag 值或时间相一 致时,则作为范围请求处理。反之,则忽略范围请求,返回全体资源。

    If-Range: "123456"
    Range: bytes=5001-10000
    
  • If-Unmodified-Since

    If-Unmodified-Since: Thu, 03 Jul 2012 00:00:00 GMT
    

    首部字段 If-Unmodified-Since 和首部字段 If-Modified-Since 的作用相 反。它的作用的是告知服务器,指定的请求资源只有在字段值内指定的日期时间之后,未发生更新的情况下,才能处理请求。如果在指定日期时间后发生了更新,则以状态码 412 Precondition Failed 作为响应返回。

响应首部字段(Response Header Fields)

从服务端向客户端返回响应报文时使用的首部。补充了响应的附加内容,也会要求客户端附加额外的内容信息

  • Accept-Ranges

    首部字段 Accept-Ranges 是用来告知客户端服务器是否能处理范围请 求,以指定获取服务器端某个部分的资源。

    可指定的字段值有两种,可处理范围请求时指定其为 bytes,反之则 指定其为 none。

    Accept-Ranges: bytes
    
  • Location

    使用首部字段Location可以将响应接收方引导至某个与请求URI位置不同的资源

    基本上,该字段会配合 3xx:Redirection的响应,提供重定向的URI。几乎所有的浏览器在接收到包含首部字段Location的响应后,都会强制性的尝试对已提示的重定向资源的访问

    Location: http://www.w3.org
    
  • Proxy-Authenticate

    代理服务器对客户端的认证信息。首部字段 Proxy-Authenticate 会把由代理服务器所要求的认证信息发送 给客户端。

    它与客户端和服务器之间的 HTTP 访问认证的行为相似,不同之处在 于其认证行为是在客户端与代理之间进行的。而客户端与服务器之间 进行认证时,首部字段 WWW-Authorization 有着相同的作用

    Proxy-Authenticate: Basic realm="Usagidesign Auth"
    
  • Server

    服务器的名字

    Server: BWS/1.1
    Server: Apache/2.4.1 (Unix)
    
  • Vary

    代理服务器缓存的管理信息

    Vary: Accept-Language
    

    首部字段 Vary 可对缓存进行控制。源服务器会向代理服务器传达关于本地缓存使用方法的命令。

    从代理服务器接收到源服务器返回包含 Vary 指定项的响应之后,若再要进行缓存,仅对请求中含有相同 Vary 指定首部字段的请求返回缓存。即使对相同资源发起请求,但由于 Vary 指定的首部字段不相同,因此必须要从源服务器重新获取资源。

  • WWW-Authenticate

    服务器对客户端的认证信息

    WWW-Authenticate: Basic realm="Usagidesign Auth"
    
  • Access-Control-Allow-Origin

    指定哪些网站可参与到跨来源资源共享过程中

    Access-Control-Allow-Origin: *
    
  • ETag

    ETag: "143-5b5f523376080"
    

    ETag 是“实体标签”(Entity Tag)的缩写,是资源的一个唯一标识,它是一种可将资源以字符串形式做唯一性标识的方式。服务器会为每份资源分配对应的 ETag 值。

    另外,当资源更新时,ETag 值也需要更新。生成 ETag 值时,并没有统一的算法规则,而仅仅是由服务器来分配。

    使用 ETag 就可以精确地识别资源的变动情况,让浏览器能够更有效地利用缓存。

    • 强 ETag 值

      不论实体发生多么细微的变化都会改变其值。

      ETag: "143-5b5f523376080"
      
    • 弱 ETag 值

      弱 ETag 值只用于提示资源是否相同。只有资源发生了根本改变,产生差异时才会改变 ETag 值。这时,会在字段值最开始处附加 W/。(例如 HTML 里的标签顺序调整,或者多了几个空格)

      ETag: W/"143-5b5f523376080"
      
  • Set-Cookie

    Cookie 最基本的用途是身份识别,实现有状态的会话事务。返回一个Cookie让客户端去保存。当服务器准备开始管理客户端状态时,会事先告知各种信息

    Cookie并不属于HTTP标准。使用的分隔符是; ,并不是 ,

    Set-Cookie: H_PS_PSSID=33213_1460_33225_33119_33060_33113_33098_33101_33183_33181_32845_33199_33237_33217_33216_33215_33185; path=/; domain=.baidu.com
    
    属性说明
    NAME=VALUE赋予Cookie的名称和值
    expires=DATECookie的有效期(若不明确指定则默认为浏览器关闭前为止)
    path=PATH将服务器上的文件目录作为Cookie的适用对象(若不指定则默认为文档所在的文件目录)
    domain=域名作为Cookie适用对象的域名(若不指定默认为创建Cookie的服务器的域名)
    Secure仅在HTTPS安全通信前才会发送Cookie
    HttpOnly加以限制,使Cookie不能被JavaScript脚本访问

实体首部字段(Entity Header Fields)

针对请求报文和响应报文的实体部分使用的首部。补充了资源内容更新时间等与实体有关的信息

  • Allow

    资源可支持的HTTP方法

    Allow: GET, HEAD
    
  • Last-Modified

    所请求的对象的最后修改日期

    Last-Modified: Wed, 09 Dec 2020 14:30:01 GMT
    
  • Expires

    指定一个时间,超过该时间则认为此响应以及过期

    Expires: Wed, 09 Dec 2020 14:30:01 GMT
    
  • Content-Type

    响应体的类型

    Content-Type: text/html;charset=utf-8
    
    Content-Type: multipart/form-data;boundary=xxx
    
  • Content-Encoding

    内容所使用的编码类型

    Content-Encoding: gzip
    
  • Content-Length

    响应体的长度(字节为单位)

    Content-Length: 348
    
  • Content-Disposition

    一个可以让客户端下载文件并建议文件名的头部

    Content-Disposition: attachment; filename="fname.ext"
    
  • Content-Range

    这条部分消息是属于完整消息的哪部分

    Content-Range: bytes 21010-47021/47022
    

HTTP状态码(Status Code)

RFC 2616 10.Status Code Definitions规范中定义。状态码指示HTTP请求是否已成功完成

状态码可以分为5类

  • 信息响应:100~199
  • 成功响应:200~299
  • 重定向:300~399
  • 客户端错误:400~499
  • 服务器错误:500~599

1xx(信息)

1xx表示接收的请求正在处理

  • 100 Continue

    请求的初始部分已经被服务器收到,并且没有被服务器拒绝。客户端应该继续发送剩余的请求,如果请求已经完成,就忽略这个响应。

    允许客户端发送带请求头的请求前,判断服务器是否愿意接收请求(服务器通过请求头判断)

    在某些情况下,如果服务器在不看请求头就拒绝请求时,客户端就发送请求体是不恰当的或低效的

2xx(成功)

2xx的响应结果表明请求被正常处理了

  • 200 OK

    表示从客户端发来的请求在服务器端被正常处理了。

    在响应报文内,随状态码一起返回的信息会因方法的不同而发生改变。比如,使用 GET 方法时,对应请求资源的实体会作为响应返 回; 而使用 HEAD 方法时,对应请求资源的实体首部不随报文主体作为响应返回(即在响应中只返回首部,不会返回实体的主体部 分)。

  • 204 No Content

    该状态码代表服务器接收的请求已成功处理,但在返回的响应报文中不含实体的主体部分。另外,也不允许返回任何实体的主体。比如, 当从浏览器发出请求处理后,返回 204 响应,那么浏览器显示的页面不发生更新。

    一般在只需要从客户端往服务器发送信息,而对客户端不需要发送新信息内容的情况下使用。

  • 206 Partial Content

    该状态码表示客户端进行了范围请求,而服务器成功执行了这部分的 GET 请求。响应报文中包含由 Content-Range 指定范围的实体内容。

3xx(重定向)

3xx 响应结果表明浏览器需要执行某些特殊的处理以正确处理请求。

  • 301 永久重定向( Moved Permanently)

    永久性重定向。该状态码表示请求的资源已被分配了新的 URI,今后所有的请求都必须改用新的 URI。

    浏览器看到 301,就知道原来的 URI“过时”了,就会做适当的优化。比如历史记录、更新书签,下次可能就会直接用新的 URI 访问,省去了再次跳转的成本。搜索引擎的爬虫看到 301,也会更新索引库,不再使用老的 URI。

  • 302 临时重定向( Moved Temporarily)

    临时性重定向。意思是原 URI 处于“临时维护”状态,新的 URI 是起“顶包”作用的“临时工”。希望用户(本次)能使用新的 URI 访问。

    浏览器或者爬虫看到 302,会认为原来的 URI 仍然有效,但暂时不可用,所以只会执行简单的跳转页面,不记录新的 URI,也不会有其他的多余动作,下次访问还是用原 URI。

    和 301 Moved Permanently 状态码相似,但 302 状态码代表的资源不是被永久移动,只是临时性质的。换句话说,已移动的资源对应的 URI 将来还有可能发生改变。比如,用户把 URI 保存成书签,但不会像 301 状态码出现时那样去更新书签,而是仍旧保留返回 302 状态码 的页面对应的 URI。

  • 303 See Other

    该状态码表示由于请求对应的资源存在着另一个 URI,应使用 GET 方法定向获取请求的资源。

    303 状态码和 302 Found 状态码有着相同的功能,但 303 状态码明确表示客户端应当采用 GET 方法获取资源,这点与 302 状态码有区别。

    比如,当使用 POST 方法访问 CGI 程序,其执行后的处理结果是希望 客户端能以 GET 方法重定向到另一个 URI 上去时,返回 303 状态码。虽然 302 Found 状态码也可以实现相同的功能,但这里使用 303状态码是最理想的

  • 304 Not Modified

    该状态码表示客户端发送附带条件的请求时,服务器端允许请求访问资源,但未满足条件的情况。304 状态码返回时,不包含任何响应的主体部分。304 虽然被划分在 3XX 类别中,但是和重定向没有关系。

    说明无需再次传输请求的内容,也就是说可以使用缓存的内容

  • 307 Temporary Redirect

    类似 302,但重定向后请求里的方法和实体不允许变动,含义比 302 更明确;

  • 308 Permanent Redirect

    类似 307,不允许重定向后的请求变动,但它是 301“永久重定向”的含义。

4xx(客户端错误)

4xx 的响应结果表明客户端是发生错误的原因所在。

  • 400 Bad Request

    该状态码表示请求报文中存在语法错误。当错误发生时,需修改请求的内容后再次发送请求。另外,浏览器会像 200 OK 一样对待该状态码。

  • 401 Unauthorized

    该状态码表示发送的请求需要有通过 HTTP 认证的认证信息。

    返回含有 401 的响应必须包含一个适用于被请求资源的 WWW- Authenticate 首部用以质询(challenge)用户信息。当浏览器初次接收 到 401 响应,会弹出认证用的对话窗口。

  • 403 Forbidden

    该状态码表明对请求资源的访问被服务器拒绝了。服务器端没有必要给出拒绝的详细理由,但如果想说明的话,可以在实体的主体部分对原因进行描述,这样就能让用户看到了。

    未获得文件系统的访问授权,访问权限出现某些问题(从未授权的发送源 IP 地址试图访问)等列举的情况都可能是发生 403 的原因。

  • 404 Not Found

    该状态码表明服务器上无法找到请求的资源。除此之外,也可以在服务器端拒绝请求且不想说明理由时使用。

  • 405 Method Not Allowed

    服务器禁止了使用当前HTTP方法的请求。例如不允许使用GET请求,需要换成POST

  • 406 Not Acceptable

    服务器无法提供与Accept-Charset以及Accept-Language指定的值相匹配的响应

  • 408 Request Timeout

    服务器想要将没有在使用的连接关闭。一些服务器会在空闲连接上发送此消息,即便是在客户端没有发送任何请求的情况下

5xx(服务器错误)

5xx 的响应结果表明服务器本身发生错误。

  • 500 Internal Server Error

    该状态码表明服务器端在执行请求时发生了错误。也有可能是 Web 应用存在的 bug 或某些临时的故障。

  • 501 Not Implemented

    请求的方法不被服务器支持,因此无法被处理。服务器必须支持的方法(即不会返回这个状态码的方法)只有 GET 和 HEAD

  • 502 Bad Gateway

    作为网关或代理角色的服务器,从上游服务器(如tomcat)中接收到的响应是无效的

  • 503 Service Unavailable

    该状态码表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。如果事先得知解除以上状况需要的时间,最好写入 RetryAfter 首部字段再返回给客户端。

HTTP缓存

缓存(Cache)是计算机领域里的一个重要概念,是优化系统性能的利器。

由于链路漫长,网络时延不可控,浏览器使用 HTTP 获取资源的成本较高。所以,非常有必要把“来之不易”的数据缓存起来,下次再请求的时候尽可能地复用。这样,就可以避免多次请求 - 应答的通信成本,节约网络带宽,也可以加快响应速度。

通常会缓存的情况是:GET请求+静态资源(比如HTML、CSS、JS、图片等)

大致流程为:

  1. 客户端第一次请求服务器获取资源,服务器响应请求,返回资源,并同时标记缓存策略以及有效期等
  2. 客户端接收请求,并缓存资源
  3. 客户端再次请求服务器时,会根据缓存策略决定是否发送请求。若本地无缓存或缓存过期,则发送请求给服务器获取最新资源
  4. 服务器接收到客户端请求,检查资源是否变更,决定返回方式

响应首部:

  • Pragma

    作用和Cache-Control相同,是HTTP/1.0的产物

  • Expires

    缓存的过期时间(GMT格式时间),也是HTTP/1.0的产物

  • Cache-Control

    缓存策略,HTTP/1.1

    • no-storage

      不允许缓存数据到本地。用于某些变化非常频繁的数据,例如秒杀页面;

    • public

      允许用户、代理服务器等缓存数据到本地

    • private

      只允许用户缓存数据到本地,代理服务器不要缓存

    • max-age

      缓存的有效时间(多长时间不过期),单位为秒

    • no-cache

      可以缓存数据到本地,但每次都需要发送请求给服务器询问缓存是否有变化,再来决定如何使用缓存

    • must-revalidate

      和 no_cache 相似的词,它的意思是如果缓存不过期就可以继续使用,但过期了如果还想用就必须去服务器验证。

  • Last-Modified

    资源的最后一次修改时间

  • ETag

    ETag是实体标签(Entity Tag)的缩写,是资源的唯一标识,主要用来解决修改时间无法准确区分文件变化的问题。

条件请求

HTTP 协议就定义了一系列“If”开头的“条件请求”字段,专门用来检查验证资源是否过期

条件请求一共有 5 个头字段,我们最常用的是“if-Modified-Since”和“If-None-Match”这两个。需要第一次的响应报文预先提供“Last-modified”和“ETag”,然后第二次请求时就可以带上缓存里的原值,验证资源是否是最新的。

如果资源没有变,服务器就回应一个“304 Not Modified”,表示缓存依然有效,浏览器就可以更新一下有效期,然后放心大胆地使用缓存了。

  • If-None-Match

    如果上一次的响应头中有ETag,就会将ETag的值作为请求头的值。如果服务器发现资源的最新摘要值跟If-None-Match不匹配,就会返回新的资源(200 OK)。否则,就会返回(304 Not Modified)代表缓存依然有效可以继续使用

  • If-Modified-Since

    如果上一次的响应头中没有ETag,有Last-Modified,就会将Last-Modified的值作为请求头的值。如果服务器发现资源的最后一次修改时间晚于If-Modified-Since,就会返回新的资源(200 OK),否则,就会返回(304 Not Modified)代表缓存依然有效可以继续使用

Last-Modified的缺陷

  • 只能精确到秒级别,如果资源在1秒内被修改了,客户端将无法获取最新的资源数据
  • 如果某些资源被修改了(最后一次修改时间发生了变化),但是内容并没有任何变化,会导致相同数据重复传输,没有使用到缓存
  • 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

ETag:

  • 只要资源的内容没有变化,就不会重复传输资源数据
  • 只要资源的内容发生了变化,就会返回最新的资源数据给客户端

Etag 是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-ModifiedETag 是可以一起使用的,服务器会优先验证 ETag,一致的情况下,才会继续比对 Last-Modified,最后才决定是否返回 304。

第一次请求:

再次请求:

全一点的流程:

HTTPS

HTTP的缺点

  • 通信使用明文(不加密),整个传输过程完全透明,任何人都能够在链路中截获,修改或伪造请求、响应报文,所以数据不具有可信性
  • 无法证明报文的完整性,所以有可能已遭篡改
  • 不验证通信方的身份,因此有可能遭遇伪装

既然HTTP不安全,那什么样的通信过程才是安全的?

通常认为,如果通信过程具备了四个特性,就可以认为是“安全”的,这四个特性是:机密性完整性身份认证不可否认

  • 机密性(Secrecy/Confidentiality)

    机密性是指对数据的“保密”,只能由可信的人访问,对其他人是不可见的“秘密”,简单来说就是不能让不相关的人看到不该看的东西。

    比如小明和小红私下聊天,但“隔墙有耳”,被小强在旁边的房间里全偷听到了,这就是没有机密性。Wireshark实际上也是利用了 HTTP 的这个特点,捕获了传输过程中的所有数据。

  • 完整性(Integrity,也叫一致性)

    完整性是指数据在传输过程中没有被窜改,不多也不少,“完完整整”地保持着原状。

    机密性虽然可以让数据成为“秘密”,但不能防止黑客对数据的修改,黑客可以替换数据,调整数据的顺序,或者增加、删除部分数据,破坏通信过程。

    比如,小明给小红写了张纸条:“明天公园见”。小强把“公园”划掉,模仿小明的笔迹把这句话改成了“明天广场见”。小红收到后无法验证完整性,信以为真,第二天的约会就告吹了。

  • 身份认证(Authentication)

    身份认证是指确认对方的真实身份,也就是“证明你真的是你”,保证消息只能发送给可信的人。

    如果通信时另一方是假冒的网站,那么数据再保密也没有用,黑客完全可以使用冒充的身份“套”出各种信息,加密和没加密一样。

    比如,小明给小红写了封情书:“我喜欢你”,但不留心发给了小强。小强将错就错,假冒小红回复了一个“白日做梦”,小明不知道这其实是小强的话,误以为是小红的,后果可想而知。

  • 不可否认(Non-repudiation/Undeniable)

    第四个特性是不可否认(Non-repudiation/Undeniable),也叫不可抵赖,意思是不能否认已经发生过的行为,不能“说话不算数”“耍赖皮”。

    使用前三个特性,可以解决安全通信的大部分问题,但如果缺了不可否认,那通信的事务真实性就得不到保证,有可能出现“老赖”。

    比如,小明借了小红一千元,没写借条,第二天矢口否认,小红也确实拿不出借钱的证据,只能认倒霉。另一种情况是小明借钱后还了小红,但没写收条,小红于是不承认小明还钱的事,说根本没还,要小明再掏出一千元。

所以,只有同时具备了机密性、完整性、身份认证、不可否认这四个特性,通信双方的利益才能有保障,才能算得上是真正的安全。

HTTPS

HTTPS为HTTP增加了上述所说的四大安全特性。我们把添加了加密及认证机制的 HTTP 称为 HTTPS (HTTP Secure)。

HTTPS 并非是应用层的一种新协议。只是 HTTP 通信接口部分用 SSL(Secure Socket Layer)和 TLS(Transport Layer Security)协议代 替而已。

HTTPS 名字里的“S”,它把 HTTP 下层的传输协议由 TCP/IP 换成了 SSL/TLS,由“HTTP over TCP/IP”变成了“HTTP over SSL/TLS”,让 HTTP 运行在了安全的 SSL/TLS 协议上。简言之,所谓 HTTPS,其实就是身披 SSL 协议这层外壳的 HTTP。

SSL/TLS

SSL 即安全套接层(Secure Sockets Layer),在 OSI 模型中处于第 5 层(会话层),由网景公司于 1994 年发明,有 v2 和 v3 两个版本,而 v1 因为有严重的缺陷从未公开过。

SSL 发展到 v3 时已经证明了它自身是一个非常好的安全通信协议,于是互联网工程组 IETF 在 1999 年把它改名为 TLS(传输层安全,Transport Layer Security),正式标准化,版本号从 1.0 重新算起,所以 TLS1.0 实际上就是 SSLv3.1。

到今天 TLS 已经发展出了三个版本,分别是 2006 年的 1.1、2008 年的 1.2 和2018年的 1.3,每个新版本都紧跟密码学的发展和互联网的现状,持续强化安全和性能,已经成为了信息安全领域中的权威标准。

目前应用的最广泛的 TLS 是 1.2,而之前的协议(TLS1.1/1.0、SSLv3/v2)都已经被认为是不安全的,各大浏览器即将在 2020 年左右停止支持,所以接下来的讲解都针对的是 TLS1.2。

TLS 由记录协议握手协议警告协议变更密码规范协议扩展协议等几个子协议组成,综合使用了对称加密、非对称加密、身份认证等许多密码学前沿技术。

浏览器和服务器在使用 TLS 建立连接时需要选择一组恰当的加密算法来实现安全通信,这些算法的组合被称为“密码套件”(cipher suite,也叫加密套件)。

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 的握手过程,其中每一个“框”都是一个记录,多个记录组合成一个 TCP 包发送。所以,最多经过两次消息往返(4 个消息)就可以完成握手,然后就可以在安全的通信环境里发送 HTTP 报文,实现 HTTPS 协议。

密码简介

对称密码

加密和解密使用同一个密钥,是对称的。只要保证了密钥的安全,那整个通信过程可以说具有了机密性

常见的有 RC4、DES、3DES、AES等,目由于前几种都是不安全的,目前对称密码最常用的就是AES

公钥密码(非对称密码)

主要为了解决对称密码中的密钥配送问题。RSA为代表

公钥密码(public-key cryptography)中,密钥分为 加密密钥 和 解密密钥 两种。发送者用加密密钥对消息进行加密,接收者用解密密钥对密文进行解密。

  • 发送者只需要加密密钥
  • 接收者只需要解密密钥
  • 解密密钥不可被窃听者获取
  • 加密密钥被窃听者获取也没问题

解密密钥由自己保管,不能公开,所以称为 私钥(private key)

加密密钥一般是公开的,所以被称为 公钥(public key)。

由于公钥密码的效率比对称密码差,所以实际使用中使用对称密码对内容进行加密,使用公钥密码解决密钥配送问题。这就是混合密码系统

单向散列函数

也叫做哈希函数、摘要算法。由于单向散列函数加密之后,内容不可复原,所以主要用来检测完整性,消息认证码、数字签名等

数字签名

数字签名是一种将相当于现实世界中的盖章、签字的功能在计算机世界中进行实现的技术。 使用数字签名可以识别篡改和伪装,还可以防止否认。在数字签名技术中,出现了下面两种行为:

  • 生成消息签名的行为
  • 验证消息签名的行为

数字签名中,生成签名和验证签名这两个行为需要使用各自专用的密钥来完成。 数字签名对签名密钥和验证密钥进行了区分,使用验证密钥是无法生成签名的。 签名密钥只能由签名的人持有,而验证密钥则是任何需要验证签名的人都可以持有

证书(CA)

公钥证书(Public-Key Certificate)其实和驾照很相似,里面记有姓名、组织、邮箱地址等个人信息,以及属于此人的公钥,并由认证机构(Certification Authority,CA)施加数字签名。只要看到公钥证书,我们就可以知道认证机构认定该公钥的确属于此人,公钥证书也简称为证书(certificate)

等于是给公钥加上了可信任的数字签名,保证了公钥的正确性

TLS握手过程

ECDHE 握手过程

  1. 客户端首先发送 Client Hello 报文开始TLS通信,也就是跟服务器打招呼。报文中包含了客户端支持的TLS版本号、支持的密码套件(Cipher Suite)列表、一个随机数(Client Random)用于后续生成会话密钥、扩展列表等

    Handshake Protocol: Client Hello
        Handshake Type: Client Hello (1)
        Length: 508
        Version: TLS 1.2 (0x0303)
        Random: 1cbf803321fd2623408dfe70d825c9dbdab33fd273f6a884…
        Session ID Length: 32
        Session ID: f655c8005ba1a4f66cd8790cac2c3847344ff3fad2629d64…
        Cipher Suites Length: 34
        Cipher Suites (17 suites)
            Cipher Suite: Reserved (GREASE) (0x1a1a)
            Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
            Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)
            Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303)
            Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
            Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
            Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c)
            Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
            Cipher Suite: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca9)
            Cipher Suite: TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca8)
            Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
            Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
            Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c)
            Cipher Suite: TLS_RSA_WITH_AES_256_GCM_SHA384 (0x009d)
            Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)
            Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0x0035)
            Cipher Suite: TLS_RSA_WITH_3DES_EDE_CBC_SHA (0x000a)
        Compression Methods Length: 1
        Compression Methods (1 method)
        Extensions Length: 401
        Extension: Reserved (GREASE) (len=0)
        ...
    
  2. 服务器收到Client Hello后,会返回一个 Server Hello 报文作为应答。报文中包含TLS版本号、一个随机数(Server Random)、然后从客户端的密码套件列表中选一个作为本次通信使用。这里选择的是 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

    Handshake Protocol: Server Hello
        Handshake Type: Server Hello (2)
        Length: 108
        Version: TLS 1.2 (0x0303)
        Random: 0e6320f21bae50842e961b78ac0761d9324595c2b8e51daf…
        Session ID Length: 32
        Session ID: 6174d101698ff8db0b0224c65d6e0aab396622a9a674c09f…
        Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
        Compression Method: null (0)
        Extensions Length: 36
        Extension: renegotiation_info (len=1)
       	...
    
  3. 然后服务器为了证明自己的身份,向客户端发送 Certificate 报文。报文中包含公开密钥证书

    TLSv1.2 Record Layer: Handshake Protocol: Certificate
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 770
        Handshake Protocol: Certificate
            Handshake Type: Certificate (11)
            Length: 766
            Certificates Length: 763
            Certificates (763 bytes)
                Certificate Length: 760
                Certificate: 308202f4308201dca003020102020900fa9c5b27a0c1368d… (id-at-commonName=www.chrono.com)
                    signedCertificate
                    algorithmIdentifier (sha256WithRSAEncryption)
                    Padding: 0
                    encrypted: 6dd90318e47d1b41728f04802a85eedca00a615feed7a67f…
    
  4. 因为服务器选择了 ECDHE 算法,所以此时会发送 Server Key Exchange 报文。里面是椭圆曲线的公钥(Server Params),用来实现密钥交换算法,再加上自己的私钥签名认证。

    TLSv1.2 Record Layer: Handshake Protocol: Server Key Exchange
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 300
        Handshake Protocol: Server Key Exchange
            Handshake Type: Server Key Exchange (12)
            Length: 296
            EC Diffie-Hellman Server Params
                Curve Type: named_curve (0x03)
                Named Curve: x25519 (0x001d)
                Pubkey Length: 32
                Pubkey: 3b39deaf00217894e8fb40e95e85a673eaf66c8103f5ed8b…
                Signature Algorithm: rsa_pkcs1_sha512 (0x0601)
                    Signature Hash Algorithm Hash: SHA512 (6)
                    Signature Hash Algorithm Signature: RSA (1)
                Signature Length: 256
                Signature: 37141adac38ea489b2959fe2e3b53751e936a48fdb929060…
    
  5. 最后服务器发送 Server Hello Done 报文告知客户端,我的消息发送完了。最初阶段的TLS握手协商部分结束

    TLSv1.2 Record Layer: Handshake Protocol: Server Hello Done
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 4
        Handshake Protocol: Server Hello Done
            Handshake Type: Server Hello Done (14)
            Length: 0
    
  6. 此时,客户端和服务器都有了三个参数:Client Random、Server Random、Server Params。客户端同时也有了服务器的证书,接下来客户端开始走证书链逐级验证,确认证书的真实性,再用证书公钥验证签名,就确认了服务器的身份。

    然后,客户端按照密码套件的要求,也生成一个椭圆曲线的公钥(Client Params),用“Client Key Exchange”消息发给服务器。

    TLSv1.2 Record Layer: Handshake Protocol: Client Key Exchange
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 37
        Handshake Protocol: Client Key Exchange
            Handshake Type: Client Key Exchange (16)
            Length: 33
            EC Diffie-Hellman Client Params
                Pubkey Length: 32
                Pubkey: 8c674d0e08dc27b5eaa9a90410e680868b99d68d4c82511e…
    
  7. 客户端和服务器此时都有了密钥交换算法的两个参数(Client Params、Server Params),然后通过ECDHE算法,算出一个新的东西,叫 Pre-Master,其实也是一个随机数。

    客户端和服务器再同时根据 Client Random、Server Random、Pre-Master三个参数,生成用于加密会话的主密钥,叫 Master Secret

  8. 算出了主密钥之后,客户端会发送 “Change Cipher Spec” 报文,告诉服务器在此报文之后的通信都改为加密通信,用的对称算法是刚开始协商时的AES

    TLSv1.2 Record Layer: Change Cipher Spec Protocol: Change Cipher Spec
        Content Type: Change Cipher Spec (20)
        Version: TLS 1.2 (0x0303)
        Length: 1
        Change Cipher Spec Message
    
  9. 然后再发一个“Finished”消息,把之前所有发送的数据做个摘要,再加密一下,让服务器做个验证。此时客户端就可以发送正常请求了

    TLSv1.2 Record Layer: Handshake Protocol: Encrypted Handshake Message
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 40
        Handshake Protocol: Encrypted Handshake Message
    
  10. 服务器也是同样的操作,发“Change Cipher Spec”和“Finished”消息,双方都验证加密解密 OK,握手正式结束,后面就收发被加密的 HTTP 请求和响应了。

    TLSv1.2 Record Layer: Change Cipher Spec Protocol: Change Cipher Spec
        Content Type: Change Cipher Spec (20)
        Version: TLS 1.2 (0x0303)
        Length: 1
        Change Cipher Spec Message
    
    TLSv1.2 Record Layer: Handshake Protocol: Encrypted Handshake Message
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 40
        Handshake Protocol: Encrypted Handshake Message
    

RSA握手过程

RSA的握手过程和ECDHE的大致相同。只是“Pre-Master”不再需要用算法生成,而是客户端直接生成随机数,然后用服务器的公钥加密,通过“Client Key Exchange”消息发给服务器。服务器再用私钥解密,这样双方也实现了共享三个随机数,就可以生成主密钥。

RSA 做密钥交换不具有“前向安全”(Forward Secrecy)。

假设有这么一个很有耐心的黑客,一直在长期收集混合加密系统收发的所有报文。如果加密系统使用服务器证书里的 RSA 做密钥交换,一旦私钥泄露或被破解(使用社会工程学或者巨型计算机),那么黑客就能够使用私钥解密出之前所有报文的“Pre-Master”,再算出会话密钥,破解所有密文。

这就是所谓的“今日截获,明日破解”。

而 ECDHE 算法在每次握手时都会生成一对临时的公钥和私钥,每次通信的密钥对都是不同的,也就是“一次一密”,即使黑客花大力气破解了这一次的会话密钥,也只是这次通信被攻击,之前的历史消息不会受到影响,仍然是安全的。

所以现在主流的服务器和浏览器在握手阶段都已经不再使用 RSA,改用 ECDHE

双向认证

上面所说的情况属于 单向认证,只认证了服务器的身份,而没有认证客户端的身份。这是因为通常单向认证通过后已经建立了安全通信,用账号、密码等简单的手段就能够确认用户的真实身份。

但为了防止账号、密码被盗,有的时候(比如网上银行)还会使用 U 盾给用户颁发客户端证书,实现“双向认证”,这样会更加安全。

双向认证的流程也没有太多变化,只是在“Server Hello Done”之后,“Client Key Exchange”之前,客户端要发送“Client Certificate”消息,服务器收到后也把证书链走一遍,验证客户端的身份。

TLS 1.3

  1. 为了兼容 1.1、1.2 等“老”协议,TLS1.3 会“伪装”成 TLS1.2,新特性在“扩展”里实现;
  2. 1.1、1.2 在实践中发现了很多安全隐患,所以 TLS1.3 大幅度删减了加密算法,只保留了 ECDHE、AES、ChaCha20、SHA-2 等极少数算法,强化了安全;
  3. TLS1.3 也简化了握手过程,完全握手只需要一个消息往返,提升了性能。

HTTP/2

2015年,HTTP/2 发布。HTTP/2是现行HTTP协议(HTTP/1.x)的替代,但它不是重写,HTTP方法/状态码/语义都与HTTP/1.x一样。HTTP/2基于SPDY,专注于性能,最大的一个目标是在用户和网站间只用一个连接(connection)。从目前的情况来看,国内外一些排名靠前的站点基本都实现了HTTP/2的部署,使用HTTP/2能带来20%~60%的效率提升。

HTTP/2由两个规范(Specification)组成:

  1. Hypertext Transfer Protocol version 2 - RFC7540
  2. HPACK - Header Compression for HTTP/2 - RFC7541

二进制传输

HTTP/2传输数据量的大幅减少,主要有两个原因:以二进制方式传输和Header 压缩。我们先来介绍二进制传输,HTTP/2 采用二进制格式传输数据,而非HTTP/1.x 里纯文本形式的报文 ,二进制协议解析起来更高效。HTTP/2 将请求和响应数据分割为更小的帧,并且它们采用二进制编码

它把TCP协议的部分特性挪到了应用层,把原来的"Header+Body"的消息"打散"为数个小片的二进制"帧"(Frame),用"HEADERS"帧存放头数据、"DATA"帧存放实体数据。HTP/2数据分帧后"Header+Body"的报文结构就完全消失了,协议看到的只是一个个的"碎片"。

HTTP/2 中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装

头部压缩

HTTP/2并没有使用传统的压缩算法,而是开发了专门的"HPACK”算法,在客户端和服务器两端建立“字典”,用索引号表示重复的字符串,还采用哈夫曼编码来压缩整数和字符串,可以达到50%~90%的高压缩率。

具体来说:

  • 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;
  • 首部表在HTTP/2的连接存续期内始终存在,由客户端和服务器共同渐进地更新;
  • 每个新的首部键-值对要么被追加到当前表的末尾,要么替换表中之前的值

多路复用

在 HTTP/2 中引入了多路复用的技术。多路复用很好的解决了浏览器限制同一个域名下的请求数量的问题,同时也接更容易实现全速传输,毕竟新开一个 TCP 连接都需要慢慢提升传输速度。

在 HTTP/2 中,有了二进制分帧之后,HTTP /2 不再依赖 TCP 链接去实现多流并行了,在 HTTP/2中,

  • 同域名下所有通信都在单个连接上完成。
  • 单个连接可以承载任意数量的双向数据流。
  • 数据流以消息的形式发送,而消息又由一个或多个帧组成,多个帧之间可以乱序发送,因为根据帧首部的流标识可以重新组装。

这一特性,使性能有了极大提升:

  • 同个域名只需要占用一个 TCP 连接,使用一个连接并行发送多个请求和响应,这样整个页面资源的下载过程只需要一次慢启动,同时也避免了多个TCP连接竞争带宽所带来的问题。
  • 并行交错地发送多个请求/响应,请求/响应之间互不影响。
  • 在HTTP/2中,每个请求都可以带一个31bit的优先值,0表示最高优先级, 数值越大优先级越低。有了这个优先值,客户端和服务器就可以在处理不同的流时采取不同的策略,以最优的方式发送流、消息和帧。

服务端推送

HTTP2还在一定程度上改变了传统的“请求-应答”工作模式,服务器不再是完全被动地响应请求,也可以新建“流”主动向客户端发送消息。比如,在浏览器刚请求HTML的时候就提前把可能会用到的JS、CSS文件发给客户端,减少等待的延迟,这被称为"服务器推送"( Server Push,也叫 Cache push)

另外需要补充的是,服务端可以主动推送,客户端也有权利选择是否接收。如果服务端推送的资源已经被浏览器缓存过,浏览器可以通过发送RST_STREAM帧来拒收。主动推送也遵守同源策略,换句话说,服务器不能随便将第三方资源推送给客户端,而必须是经过双方确认才行。