前端面试基础知识总结(六):计算机网络

OSI七层模型

OSI七层模型

简介

OSI七层模型

  • 应用层:提供各种网络服务协议
  • 表示层:用于应用层数据的编码和转换功能
  • 会话层:负责建立、管理和终止表示层实体之间的通信会话
  • 传输层:提供端到端的可靠和透明的数据传输服务
  • 网络层:通过IP寻址来建立两个节点之间的连接
  • 数据链路层:传输帧
  • 物理层:传输比特流

应用层常见的网络协议

  • HTTP 超文本传输协议
  • SMTP\POP3 邮件发送\接收协议
  • FTP 文件传输协议
  • DNS 域名系统

TCP与UDP的区别

TCP协议(传输控制协议)是一种传输层协议,允许数据包从一个位置发送到另一个位置。TCP 是面向连接的协议,也就是说它在网络计算机单元之间的任何通信之前建立连接;同时这个连接将一直保持,直到发送方和接收方完成数据交换。这就是下面介绍的三次握手,四次挥手的过程。

在UDP协议(用户数据报协议)中,接收方不生成数据包的确认,发送方也不等待数据包的确认。正是这个不足,使得该协议虽不可靠但是速度更快。

区别 TCP协议 UDP协议
连接 面向连接协议 无连接协议
可靠性 可靠。接收数据失败时,它会请求重新传输。 失败时不充发请求。依赖于高层协议来确保可靠性。
保持数据传输的顺序 有序接收数据包 无序接收数据包
传输速度 头部开销大,传输更慢 头部开销小,传输更快
用例 用于 HTTPS、HTTP、SMTP、FTP等等。 用于视频流、视频电话、IP 语音服务(互联网呼叫)、DNS等。

DNS 域名系统

DNS 域名系统提供域名到IP地址的转换。

解析流程:

  • 优先读取浏览器缓存,系统缓存,本地hosts缓存
  • 没有再找域名解析服务器,分级查询

DNS解析流程

举个例子:

www.tmall.com对应的真正的域名为www.tmall.com.。末尾的.称为根域名,因为每个域名都有根域名,因此我们通常省略。
根域名的下一级,叫做”顶级域名”(top-level domain,缩写为TLD),比如.com、.net;
再下一级叫做”次级域名”(second-level domain,缩写为SLD),比如www.tmall.com里面的.tmall,这一级域名是用户可以注册的;
再下一级是主机名(host),比如www.tmall.com里面的www,又称为”三级域名”,这是用户在自己的域里面为服务器分配的名称,是用户可以任意分配的。

DNS资源记录:用于把域名解析到相应 IP 地址上

DNS资源记录

  • 记录类型中 A 记录指向 IP 地址,CNAME 记录指向域名,常用于 CDN
  • TTL(生存周期):一般的域名解析商都有默认值,阿里云为10分钟

TCP三次握手、四次挥手

TCP三次握手、四次挥手

TCP 通过三次握手建立一个连接,四次挥手关闭一个连接。这也是 TCP 协议面向连接的深层含义。

三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。

TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),客户端或服务器均可主动发起挥手动作。前两次挥手,只是断开连接这件事做确认,但并不会立即行这件事。第三次挥手前,服务器会把自己想传的数据传输完,然后再通知一次客户端。第四次挥手,客户端接收并响应来自服务端的分手请求。

建立一个连接需要三次握手,而终止一个连接要经过四次挥手。为什么终止时会多一次呢?

  • 这由TCP的半关闭(half-close)造成的。所谓的半关闭,其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。

概括如下:

三次握手 四次挥手
前两次 对创建连接这件事做确认 对断开连接这件事做确认
第三次 创建连接 服务器最后一次传输需要的数据
第四次 / 关闭连接

【高阶】三次握手

首先,先解释一下三次握手过程中会出现的报文:

  • SYN 同步报文:SYN 置1就表示这是一个连接请求或连接接受报文
  • ACK 确认报文:只有ACK=1时有效,也规定连接建立后所有发送的报文的ACK必须为1。
  • seq 序列号:客户端、服务端都要初始序列号,第二个报文段则 + 1
  • ack 确认码:接受到的对方的序列号 + 1

三次握手的过程:

  1. 客户端:首部的同步位SYN=1,初始序号seq=x(指定客户端的初始化序列号)。
  2. 服务端:在确认报文段中SYN=1,ACK=1,确认号ack=x+1(把客户端的初始化序列号 + 1作为ACK的值),初始序号seq=y(指定服务端的初始化序列号)。
  3. 客户端:确认报文段ACK=1,确认号ack=y+1,序号seq=x+1(初始为seq=x,第二个报文段所以要+1)。

三次握手

【高阶】四次挥手

其中,FIN报文用来释放一个连接。FIN=1 时,就表示此报文段的发送方的数据已经发送完毕,请求释放运输连接。

四次挥手的过程:

  1. 客户端:FIN=1请求释放连接,seq=u(指定客户端的序列号)。
  2. 服务端:在确认报文段中ACK=1,确认号ack=u+1,初始序号seq=v(指定服务端的初始化序列号)。
  3. 服务端:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个新的序列号。服务端发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1)。
  4. 客户端:确认报文段(ACK=1,seq=u+1,ack=w+1)

四次挥手

HTTP

HTTP

对HTTP无状态的理解

HTTP是无状态的,意味着两个请求之间毫无关系。如果想要维持不同请求间的状态信息,需要其他手段,比如cookie-session,这部分内容会在前后端鉴权中介绍。

报文结构

请求报文的结构如下:

  • 请求行:请求方法、URL以及协议版本。它们用空格分隔,如GET /index.html HTTP/1.1
  • 请求头部:请求头部由关键字/值对组成,每行一对,关键字和值用英文冒号“:”分隔
  • 请求体:post、put等请求携带的数据

请求报文的报文结构

响应报文的结构如下:

  • 状态行:协议版本,状态码,状态码描述。如HTTP/1.1 200 OK
  • 响应头部:响应首部组成。
  • 响应正文:服务器响应的数据。

常见的首部字段

通用首部字段(General Header Fields):请求报文和响应报文两方都会使用的首部

常见的请求头:

  • Authorization: 验证信息
  • Accept: 浏览器能够接收的内容类型,如 text/html
  • Accept-Encoding: 浏览器能解码的编码类型,如gzip, deflate
  • Connection: 是否需要持久连接,HTTP1.1默认值为keep-alive
  • Cookie: 会话追踪
  • Host:请求资源所在服务器
  • User-Agent: 用户代理信息
  • Content-Type: GET请求无该字段,POST请求常见值有:application/x-www-form-urlencoded, multipart/form-data
  • If-Match:比较实体标记(ETage)
  • If-Modified-Since:比较资源更新时间(Last-Modified)

常见的响应头:

  • Last-Modified: 资源在服务器的最后修改时间
  • ETag:能够表示资源唯一资源的字符串
  • Expires: 相应过期的时间
  • Set-Cookie: 设置Cookie
  • Content-Encoding: 文档的编码方法
  • Content-Length: 内容的长度
  • Content-Type: 文档的MIME类型

HTTP请求方法

HTTP请求方法及作用

HTTP1.0 定义了三种请求方法:GET, POST 和 HEAD 方法。
HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。

HTTP Methods 描述 安全性 幂等性
GET 仅用作数据的读取
HEAD 只请求页面的首部,不请求页面内容,用于检查源有效性
POST 创建新资源或修改现有资源 x x
PUT 从客户端向服务器传送的数据取代指定的文档的内容 x
PATCH 是对 PUT 方法的补充,用来对已知资源进行局部更新 x x
DELETE 删除指定的资源 x
OPTIONS 获取指定服务能够支持的通信选项
CONNECT HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器 x x
TRACE 回显服务器收到的请求,主要用于测试或诊断

安全性:指该方法多次调用不会产生副作用,即不改变资源状态。

  • 安全的方法:GET,HEAD,OPTIONS和TRACE

幂等性:该方法多次调用返回的结果一致。

  • 幂等的方法:GET,HEAD,OPTIONS,TRACE,PUT和DELETE。
  • 所有安全的HTTP方法都是幂等的,但PUT和DELETE是幂等的,但并不安全。

有疑问可以看看这篇文章:POST,PUT和PATCH的区别

HTTP状态码

分类 分类描述
1xx 信息性,服务器收到请求,需要请求者继续执行操作
2xx 成功,操作被成功接收并处理
3xx 重定向,需要进一步的操作以完成请求
4xx 客户端错误,请求包含语法错误或无法完成请求
5xx 服务器错误,服务器在处理请求的过程中发生了错误

常见的HTTP状态码列表:

状态码 状态码英文名称 描述
100 Continue 继续。客户端应继续其请求
200 OK 请求成功。一般用于GET与POST请求
204 No Content 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
206 Partial Content 部分内容。服务器成功处理了部分GET请求
301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
304 Not Modified 协商缓存生效。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。
400 Bad Request 客户端请求的语法错误,服务器无法理解
401 Unauthorized 请求要求用户的身份认证
403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求
404 Not Found 服务器无法根据客户端的请求找到资源(网页)
500 Internal Server Error 服务器内部错误,无法完成请求
502 Bad Gateway 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求

HTTP/2/3

HTTP/2.0

HTTP/1.0的痛点

  1. 短连接:TCP消耗大
  2. 队头阻塞问题:每个TCP同时只能处理一个HTTP请求,浏览器遵循FIFO原则,如果上一个没返回后续请求会被阻塞。

HTTP/1.1的优化

  1. 实现长连接:减少TCP连接消耗:
    • 请求头配置:Connection:keep-alive = true。只要tcp连接不断开(默认2小时),一直可以进行http请求,但是一个tcp连接同一时间只支持一个http请求
  2. 线管化:允许多个HTTP请求批量地提交给服务器
    • 这样做不能从根本上解决队头阻塞问题(虽然发送请求可以并行了,但响应还是串行)

HTTP/2.0的优化

  1. 多路复用:一个tcp可以并发多个http请求
    • 理论无上限,但是一般浏览器会有tcp并发数的限制,chrome为6个。
  2. 二进制分帧:在应用层(HTTP)和传输层(TCP)之间增加一个二进制分帧层,从而改进传输性能。
    • HTTP 1.1在应用层以纯文本的形式进行通信;而HTTP 2.0将所有的传输信息分割为更小的消息和帧,并对它们采用二进制格式编码。
    • 在二进制分帧层上,HTTP 1.1的首部信息会被封装到Headers帧,而Request Body则封装到Data帧。
  3. 头部压缩
    • 原因:请求头字段很多都是重复的,比如Cookie,会浪费很多带宽。所以,对于相同的头部,只需发送一次即可。
    • 压缩机制:
      • 客户端通过 gzip 和 compress 压缩头部然后再发送请求。
      • 客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,产生一个索引号,之后就不发送同样字段了,只需发送索引号。
  4. 支持服务端推送
    • 客户端发送一个请求,服务器根据客户端的请求,提前返回多个响应,这样客户端就不用发起后续请求。
    • 例子:如果一个请求是由主页发送的,服务器可能会响应主页内容、logo以及样式表,因为它知道客户端会用到这些。

HTTP/3.0

  • 更快的连接建立:HTTP/3 的一个重要区别是它在一种新的传输协议 QUIC 上运行。QUIC 专为移动密集型互联网使用而设计,在这种环境中,人们携带的智能手机会在一天中不断地从一个网络切换到另一个网络。QUIC 的使用意味着 HTTP/3 依赖于UDP协议,而不是TCP协议。切换到 UDP 将使在线浏览时的连接速度和用户体验更快。
  • 零往返时间 (0-RTT):对于它们已经连接的服务器,客户端可以跳过握手要求(互相确认和验证以确定它们将如何通信的过程)
  • 更全面的加密:QUIC 默认在传输层设置加密连接——应用程序层数据将始终被加密。

HTTPS

HTTPS

HTTP的缺点

HTTP的缺点 HTTPS的改进
通信使用明文,可能会被窃听 加密
不验证通信方的身份,可能遭遇伪装 认证
无法证明报文完整性,可能已遭篡改 完整性保护

HTTPS的改进

HTTPS = HTTP + 加密 + 认证 + 完整性保护。

HTTPS 不是应用层的新协议,而是 HTTP 通信接口部分用 SSL 和 TLS 协议替换了。HTTPS先和SSL通信,再由SSL和TCP通信。

TLS 和 SSL 的区别:
TLS 是传输层安全的缩写,SSL 是安全套接字层的缩写,都是验证连接的加密协议
TLS 实际上只是 SSL 的更新版本,它修复了早期 SSL 协议中的一些安全漏洞。

非对称密钥加密技术

区别 对称密钥加密 非对称密钥加密
算法 加密解密使用相同的密钥 加密解密使用一对非对称的密钥:私有密钥 + 公开密钥。
1. 公开密钥加密,私有密钥解密。
2. 私有密钥签名,公开密钥验签。
优势 算法更简单,运行效率高 安全性高,不必担心私有密钥被窃听,内容被解密

HTTPS采用混合加密机制

HTTPS充分利用两种技术的优势,组合起来用于通信:

  1. 客户端获取公钥
    • 服务端生成公私钥对,并把公钥安全地传输给客户端
  2. 在交换密钥阶段:非对称加密
    • 客户端用公钥加密 Key,服务端用私钥解密。
    • 目的:安全地交换稍后要用到的密钥 Key。
  3. 建立通信交换报文阶段:对称加密
    • 确保交换的密钥 Key 是安全的后,使用对称加密方式通信。

混合加密的机制

但是第一阶段仍有问题,那就是无法证明客户端获得的公钥确实是服务器发行的公钥。数字证书认证机构(CA)的出现解决了这个问题。

数字证书认证机构

数字证书认证机构(CA,Certificate Authority)是客户端与服务端双方都信赖的第三方机构。

业务流程如下:

  1. 服务端将自己的公钥传给CA
  2. CA用自己的私钥对服务器公钥签名,并颁发公钥证书(服务器公钥 + CA的数字签名)
  3. 客户端拿到公钥证书,使用CA的公钥对签名验签,确认服务器公钥的真实性

那么如何拿到CA的公钥呢?

  • 多数浏览器内置常用认证机构的公钥。

其他

应用层发送数据时会附加一种叫做MAC的报文摘要。MAC能够查知报文是否已经遭到篡改,从而保护报文的完整性。

前端请求方式

混合加密的机制

Ajax

Ajax(Asynchronous JavaScript And XML,即异步JavaScript和XML),这一技术能够向服务器请求额外的数据而无须卸载页面,会带来更好的用户体验。Ajax 有两种原生的实现规范:

  1. XMLHttpRequest(XHR):现代浏览器,最开始与服务器交换数据,都是通过 XMLHttpRequest 对象。
  2. fetch:ES6后出现,基于 promise 设计。

狭义上的Ajax专指基于原生的XHR的实现方式。

XHR

缺点:

  • 使用起来也比较繁琐,需要设置很多值
  • 早期的IE浏览器有自己的实现,这样需要写兼容代码

fetch

fetch API提供了一个 JavaScript 接口,用于访问和操作HTTP管道的部分,例如请求和响应。它还提供了一个全局fetch()方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。

优势:跨域的处理,在配置中添加mode: 'no-cors'就可以跨域了

fetch目前遇到的问题:

  • fetch只对网络请求报错,对400,500都当做成功的请求,需要封装去处理
  • fetch默认不会带cookie,需要添加配置项。
  • fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费。
  • fetch没有办法原生监测请求的进度,而XHR可以。

Axios

Axios是一个基于promise的HTTP库,可以用在浏览器和 node.js 中。它本质也是对原生XMLHttpRequest的封装,只不过它是Promise的实现版本,符合最新的ES规范。

优点:

  • 从浏览器中创建XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御 CSRF

缺点:

  • 只持现代代浏览器

跨域及解决方案

跨域及解决方案

什么是跨域

跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,是广义的,通常包括:

  1. 资源跳转:A链接、重定向
  2. 资源嵌入:<script>,<frame><link><img>等dom标签
  3. 脚本请求:js发起的ajax请求、dom和js对象的跨域操作等

我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。

浏览器同源策略

同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指”协议+域名+端口”三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

同源策略限制以下几种行为:

  1. Cookie、LocalStorage 和 IndexDB 无法读取
  2. DOM 和 Js 对象无法获得
  3. AJAX 请求不能发送

这里提到的 Cookie 作用域有点特殊,详见下文。

Cookie的作用域

Cookie有两个很重要的属性:Domain和Path,用来指示此Cookie的作用域:

  • Domain:当前要添加的Cookie的域名归属,如果没有明确指明则默认为当前域名
  • Path:当前要添加的Cookie的路径归属,如果没有明确指明则默认为当前路径

浏览器提交的Cookie需要满足以下两点:

  1. 当前域名或者父域名下的Cookie;
  2. 当前路径或父路径下的Cookie。

注意:在浏览器看来. www.domain.com不是test.domain.com的父域名,而domain.com才是test.domain.com的父域名.

跨域解决方案

主要介绍以下四种:

  1. 跨域资源分享(CORS)
  2. JSONP 跨域
  3. Nginx 代理跨域
  4. postMessage 跨域

CORS

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。 CORS需要浏览器和服务器同时支持。
浏览器将CORS跨域请求分为简单请求和非简单请求。

简单请求和非简单请求的区别

简单请求和非简单请求的概念是针对跨源域资源共享而言的。跨源域资源共享(CORS)机制允许 Web 应用服务器进行跨源访问控制,从而使跨源数据传输得以安全进行。

其中,简单请求不会触发 CORS 预检请求,而非简单请求会触发预检请求(即该请求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求)。

若请求满足所有下述条件,则该请求可视为“简单请求”:

  • 请求方法:GET、HEAD、POST
  • 除了被用户代理自动设置的首部字段(例如 Connection,User-Agent)和在 Fetch 规范中定义为禁用首部名称的其他首部,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合。该集合为:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type:只限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain
  • 请求中的任意 XMLHttpRequest 对象均没有注册任何事件监听器;XMLHttpRequest 对象可以使用 XMLHttpRequest.upload 属性访问。
  • 请求中没有使用 ReadableStream 对象。

简单请求

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

CORS请求设置的响应头字段,都以 Access-Control-开头:

  1. Access-Control-Allow-Origin:必选,
    • 它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
  2. Access-Control-Allow-Credentials:可选
    • 它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。
  3. Access-Control-Expose-Headers:可选
    • CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。

非简单请求

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。服务端根据预检请求选择是否允许该跨域请求。

预检请求用的请求方法是OPTIONS,表示这个请求是用来询问的。请求头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,”预检”请求的头信息包括两个特殊字段:

  1. Access-Control-Request-Method:必选
    • 用来列出浏览器的CORS请求会用到哪些HTTP方法。
  2. Access-Control-Request-Headers:可选
    • 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段。

预检请求的响应:

服务器收到”预检”请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

请求响应中,除了关键的Access-Control-Allow-OriginAccess-Control-Allow-Credentials字段,其他CORS相关字段如下:

  1. Access-Control-Allow-Methods:必选
    • 它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次”预检”请求。
  2. Access-Control-Allow-Headers
    • 如果浏览器请求包括Access-Control-Request-Headers字段,则该字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。
  3. Access-Control-Max-Age:可选
    • 用来指定本次预检请求的有效期,单位为秒。

JSONP 跨域

同源限制是跨域限制的本质。存在一些标签没有同源限制,像<script><link><img>等标签。jsonp的原理是通过script标签跳过同源限制。基于此原理,可以通过动态创建<script>,再请求一个带参网址实现跨域通信。

实现

  1. 客户端:定义 callback,callback内是读取数据的逻辑。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <script>
    var script = document.createElement('script');
    script.type = 'text/javascript';

    // 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
    script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
    document.head.appendChild(script);

    // 回调执行函数
    function handleCallback(res) {
    alert(JSON.stringify(res));
    }
    </script>
  2. 服务端:输出对 callback 的调用(即handleCallback(res)),把目标数据作为入参传给 callback

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var http = require('http');
    var urllib = require('url');

    var port = 8080;
    // 目标数据
    var data = {'data':'world'};

    http.createServer(function(req,res){
    var params = urllib.parse(req.url,true);
    if(params.query.callback){
    // 把目标数据作为入参传给 callback
    var str = params.query.callback + '(' + JSON.stringify(data) + ')';
    res.end(str);
    } else {
    res.end();
    }

    }).listen(port,function(){
    console.log('jsonp server is on');
    });
  3. 客户端:接收到返回的 JS 脚本,开始解析和执行handleCallback(res)

Nginx 代理跨域

跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。

实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口。并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# proxy服务器
server {
listen 81;
server_name www.domain1.com;

location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;

# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}

postMessage 跨域

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

  • 多窗口之间消息传递
  • 页面与嵌套的iframe消息传递

用法:postMessage(data, origin)方法接受两个参数:

  • data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
  • origin: 协议+主机+端口号,也可以设置为”*”,表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为”/“。

例子

1). a.html:(www.domain1.com/a.html)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
name: 'aym'
};
// 向domain2传送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
};

// 接受domain2返回数据
window.addEventListener('message', function(e) {
alert('data from domain2 ---> ' + e.data);
}, false);
</script>

2). b.html:(www.domain1.com/b.html)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
// 接收domain1的数据
window.addEventListener('message', function(e) {
alert('data from domain1 ---> ' + e.data);

var data = JSON.parse(e.data);
if (data) {
data.number = 16;

// 处理后再发回domain1
window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
}
}, false);
</script>

前后端鉴权

web-jwt-session

认证与授权的关系

  • 认证(Authentication)
    认证涉及一方应用和一方用户,用于描述用户在该应用下的身份。认证可以简单理解为登录,以此确认你是一个合法的用户。
  • 授权(Authorisation)
    授权涉及两方应用和一方用户,用于描述第三方应用有哪些操作权限。

常见鉴权方式

Session-Cookie 的认证流程如下:

  1. 用户向服务器发送用户名和密码。
  2. 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
  3. 服务器向用户返回一个 session_id,写入用户的 Cookie。
  4. 用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
  5. 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

简单来说:

  • Session 保存在服务端,是单用户的会话状态。
  • Cookie 保存在客户端,每次向服务器发送请求是都会带上这个cookie。

Token认证

与上面的Session-Cookie 机制不同的地方在于,基于 token 的用户认证是一种服务端无状态的认证方式,服务端可以不用存放token 数据,但是服务器可以验证 token 的合法性和有效性。

使用token 进行认证的方式主要有两种:SAML 和 JWT。这里介绍JWT(Json Web Token)。

JWT 的原理

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。

1
2
3
4
5
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2018年7月1日0点0分"
}

以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。

JWT 的结构
  • Header(头部):声明类型和加密算法
  • Payload(负载):存放有效信息的地方
  • Signature(签名):对前两部分的签名,防止数据篡改

1.Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

1
2
3
4
{
"alg": "HS256", // 表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256)
"typ": "JWT" // 表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT
}

最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

2.Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。除了官方字段,也可以定义私有字段。也要使用 Base64URL 算法转成字符串。

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

3.Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

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

使用场景
  1. 有效期短,只希望被使用一次。

    比如,用户注册后发一封邮件让其激活账户,通常邮件中需要有一个链接,这个链接需要具备以下的特性:能够标识用户,该链接具有时效性(通常只允许几小时之内激活),不能被篡改以激活其他可能的账户,一次性的。

  2. jwt中的payload是base64编码的,没有加密,因此不能存储敏感数据

OAuth2.0 授权

允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息。

运行流程

OAuth 2.0的运行流程如下图,摘自RFC 6749:

+--------+                               +---------------+
|        |--(A)- Authorization Request ->|   Resource    |
|        |                               |     Owner     |
|        |<-(B)-- Authorization Grant ---|               |
|        |                               +---------------+
|        |
|        |                               +---------------+
|        |--(C)-- Authorization Grant -->| Authorization |
| Client |                               |     Server    |
|        |<-(D)----- Access Token -------|               |
|        |                               +---------------+
|        |
|        |                               +---------------+
|        |--(E)----- Access Token ------>|    Resource   |
|        |                               |     Server    |
|        |<-(F)--- Protected Resource ---|               | 
+--------+                               +---------------+

           Figure 1: Abstract Protocol Flow

重点在于B步骤:用户怎样才能给于客户端授权。有了这个授权以后,客户端就可以获取令牌,进而凭令牌获取资源。

授权模式

OAuth 2.0定义了四种授权方式:授权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner password credentials)、客户端模式(client credentials)。

授权码模式是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与”服务提供商”的认证服务器进行互动。

 +----------+
 | Resource |
 |   Owner  |
 |          |
 +----------+
      ^
      |
     (B)
 +----|-----+          Client Identifier      +---------------+
 |         -+----(A)-- & Redirection URI ---->|               |
 |  User-   |                                 | Authorization |
 |  Agent  -+----(B)-- User authenticates --->|     Server    |
 |          |                                 |               |
 |         -+----(C)-- Authorization Code ---<|               |
 +-|----|---+                                 +---------------+
   |    |                                         ^      v
  (A)  (C)                                        |      |
   |    |                                         |      |
   ^    v                                         |      |
 +---------+                                      |      |
 |         |>---(D)-- Authorization Code ---------'      |
 |  Client |          & Redirection URI                  |
 |         |                                             |
 |         |<---(E)----- Access Token -------------------'
 +---------+       (w/ Optional Refresh Token)

步骤如下:

(A)用户访问客户端,后者将前者导向认证服务器。

(B)用户选择是否给予客户端授权。

(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的”重定向URI”(redirection URI),同时附上一个授权码。

(D)客户端收到授权码,附上早先的”重定向URI”,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。

(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。

下面是这些步骤所需要的参数:

(A)客户端申请认证的URI,包含以下参数:

  • response_type:表示授权类型,必选项,此处的值固定为”code”
  • client_id:表示客户端的ID,必选项
  • redirect_uri:表示重定向URI,可选项
  • scope:表示申请的权限范围,可选项
  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。(用于预防CSRF攻击)
1
2
3
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

(C)服务器回应客户端的URI,包含以下参数:

  • code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。

下面是一个例子。

1
2
3
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz

D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:

  • grant_type:表示使用的授权模式,必选项,此处的值固定为”authorization_code”。
  • code:表示上一步获得的授权码,必选项。
  • redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。
  • client_id:表示客户端ID,必选项。

下面是一个例子。

1
2
3
4
5
6
7
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

E步骤中,认证服务器发送的HTTP回复,包含以下参数:

  • access_token:表示访问令牌,必选项。
  • token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。
  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
  • refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。如果用户访问的时候,客户端的”访问令牌”已经过期,则需要使用”更新令牌”申请一个新的访问令牌。
  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。

下面是一个例子。

1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}

从上面代码可以看到,相关参数使用JSON格式发送(Content-Type: application/json)。此外,HTTP头信息中明确指定不得缓存。

面试题

输入一个 url 到浏览器展示都经历了哪些过程

  1. 浏览器查看缓存

    • 强缓存:如果已缓存且足够新鲜(Expires 和 Cache-Control)直接提供给客户端
    • 协商缓存:否则与服务器进行验证(Etag和If-Modified)
  2. DNS解析

    • 分别查看浏览器、本机、hosts、路由缓存、ISP缓存
    • 没有的话做DNS递归查询
  3. TCP 连接,TCP 三次握手

    1. 在客户端发送数据前会发起 TCP 三次握手用以同步客户端和服务端的序列号和确认号,并交换 TCP 窗口的大小信息。
      • 客户端发送SYN=1,Seq=X
      • 服务器发送SYN=1,ACK=X+1,Seq=Y
      • 课后端发送ACK=y+1,Seq=Z
    2. 三次握手的目的是:保证服务器和客户端都经历了一次请求和一次响应,数据能可靠传输。
    3. 为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
  4. 发送 HTTP 请求

    • 服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码
  5. 服务器处理请求并返回 HTTP 报文

  6. 浏览器解析渲染页面

    1. 解析HTML文档,构建DOM树
      • 令牌化:根据HTML规范将字符流解析为标记流
      • 词法分析:将标记转换为对象,并定义属性和规则
      • 构建DOM:根据HTML标记关系将对象组成DOM树
    2. 解析CSS,构建CSSOM树
    3. 根据DOM树和CSSOM树构建渲染树
      • 从DOM树根节点遍历所有可见节点
      • 对可见节点,应用相应的CSSOM规则
      • 发布可视节点的内容和计算样式
    4. 执行JS脚本
  7. 断开连接,TCP 四次挥手

    1. 关闭TCP连接的过程
      1. 主动⽅发送Fin=1, Ack=Z, Seq= X报⽂
  8. 被动⽅发送ACK=X+1, Seq=Z报⽂

    1. 被动⽅发送Fin=1, ACK=X, Seq=Y报⽂
  9. 主动⽅发送ACK=Y, Seq=X报⽂

  10. 目的是:

    • 前两次挥手,只是断开连接这件事做确认,但并不会立即行这件事
      • 第三次挥手前,服务器会把自己想说的话说完,然后再通知一次客户端。
      • 第四次挥手,客户端接收并响应来自服务端的分手请求

参考

  1. tcp vs udp
  2. 面试官,不要再问我三次握手和四次挥手
  3. 面试官(9):可能是全网最全的http面试答案
  4. MDN: CORS
  5. 菜鸟教程:HTTP Methods
  6. HTTP有哪些保证幂等性和安全性的方法? - mscharhag
  7. 菜鸟教程:HTTP 状态码
  8. 前后端鉴权二三事
  9. JSON Web Token 入门教程
  10. 理解OAuth 2.0
  11. 前端常见跨域解决方案(全)