不去美化没有走过的那条路,高高兴兴打好手上剩下的牌
大家好,我是柒八九。
今天,我们继续2023前端面试真题系列。我们来谈谈关于网络的相关知识点。
如果,想了解该系列的文章,可以参考我们已经发布的文章。如下是往期文章。
文章list
你能所学到的知识点
- 缓存 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
- HTTP头 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
- ETag 推荐阅读指数⭐️⭐️⭐️
- 跨域原理与解决办法 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
- 验证码的原理和防范原因 推荐阅读指数⭐️⭐️⭐️
- HTTPS 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
- 常见的 http状态码和请求头 推荐阅读指数⭐️⭐️⭐️
- 用户登录是如何实现的 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
- 前端安全知识 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
- HTTP X.X 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
- WebSocket协议 推荐阅读指数⭐️⭐️⭐️
- DNS协议 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
- TCP协议 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
好了,天不早了,干点正事哇。
缓存
缓存:何时起作用
HTTP缓存是作用于网站导航阶段的网络请求的开始阶段
- 在
SericeWorker
之后Socket
进行DNS
查询之前
HTTP缓存策略
最好最快的请求就是没有请求
浏览器对静态资源的缓存本质上是 HTTP
协议的缓存策略,其中又可以分为
- 强制缓存
- 根据过期时间决定使用本地缓存还是请求新资源
- 协商缓存。
- 每次都会发出请求,经过服务器进行对比后决定采用本地缓存还是新资源
两种缓存策略都会将资源缓存到本地
Expires 和 Cache-control:max-age=x(强缓存)
Expires
和Cache-control:max-age=x
是强制缓存策略的关键信息,两者均是响应首部信息(后端返给客户端)的。
Expires
是 HTTP 1.0
加入的特性,通过指定一个明确的时间点作为缓存资源的过期时间,在此时间点之前客户端将使用本地缓存的文件应答请求,而不会向服务器发出实体请求。
Expires
的优点:
- 可以在缓存过期时间内减少客户端的
HTTP
请求 - 节省了客户端处理时间和提高了
Web
应用的执行速度 - 减少了服务器负载以及客户端网络资源的消耗
Expires
一个致命的缺陷是:
- 它所指定的时间点是以服务器为准的时间
- 但是客户端进行过期判断时是将本地的时间与此时间点对比
如果客户端的时间与服务器存在误差,那么通过 Expires
控制的缓存资源将会失效,客户端将会发送实体请求获取对应资源。
针对这个问题, HTTP 1.1
新增了 Cache-control
首部信息以便更精准地控制缓存。
常用的 Cache-control
信息有以下几种。
no-cache
:no-cache
将会和服务器进行一次通讯
no-store
no-store
要求资源每次都被请求并且下载下来
max-age=<seconds>
- 指定从请求的时刻开始计算,此响应的缓存副本有效的最长时间(单位:秒)
- 例如,
max-age=360
表示浏览器在接下来的 1 小时内使用此响应的本地缓存,不会发送实体请求到服务器
max-age
指定的是缓存的时间跨度,而非缓存失效的时间点,不会受到客户端与服务器时间误差的影响。
与 Expires
相比, max-age
可以更精确地控制缓存,并且比 Expires
有更高的优先级
强制缓存策略下(
Cache-control
未指定no-cache
和no-store
)的缓存判断流程
协商缓存
发生时机 浏览器
- 在
Cache-control
没有指定no-cache
- 或者
max-age
和Expires
均过期之后
协商缓存是服务器用来确定缓存资源是否可用过期
因为服务器需要向浏览器确认缓存资源是否可用,二者要进行通信,而通信的过程就是发送请求,所以在header
中就需要有专门的标识来让服务器确认请求资源是否可以缓存访问,所以就有了下面两组header
字段:
Etag
和If-None-Match
Last-Modified
和If-Modified-Since
Last-Modified/If-Modify-Since
Last-Modified
当浏览器第一次向服务器请求资源时,服务器会在该资源的请求头中加上Last-Modified
,Last-Modified
是该资源在服务器的最新修改时间,也就是说Last-Modified
记录了该资源的最后一次创建、修改时间,保证服务器给浏览器的资源是最新的。
If-Modify-Since
然后当浏览器再次请求这个资源时,会在请求报文中带上If-Modify-Since
If-Modify-Since
是浏览器上一次请求该资源时返回的Last-Modified
时间,也就是说Last-Modified
和If-Modify-Since
是同一个时间
当服务器收到
If-Modify-Since
时间后,会判断这个资源的当前最新修改时间和If-Modify-Since
时间是否相等,相等则说明浏览器缓存的资源已经是最新的了,服务器返回304
状态码告诉浏览器资源已是最新不用更新
不相等则说明在浏览器没有请求的这段时间,这个资源已经进行了修改、更新,浏览器本地缓存的这个资源已经不是最新的了,这时候服务器重新返回该资源的最新版以及最新的Last-Modified
时间。
Etag/If-None-Match
Etag/If-None-Match
和Last-Modified/If-Modify-Since
的处理逻辑一致,通俗讲都是第一次请求给个标识,然后下一次请求再把这个标识传回去,服务器判断资源改变没有,改变了就重新返回,没有改变就返回304,浏览器继续使用本地缓存。
Etag
是服务器自动生成或者由开发者生成的资源在服务器的唯一标识符,能够更加准确的控制缓存
Etag/If-None-Match
的优先级高于Last-Modified/If-Modify-Since
HTTP头(8个)
GET
和 POST
比较
幂等
HTTP/1.1
中对幂等性的定义是:一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。
get/post 请求参数位置
针对get/post
参数位置,一般来讲:
GET
请求提交的参数体现在url中POST
请求的参数通过请求体提交
但是,GET请求和POST请求本质上是一样的,参数都可以通过url或者请求体来提交,但是并不能确保参数都可以收到。
在实际开发中,除非遇到特殊情况,GET
请求使用url传递参数,POST
请求参数通过请求体提交。
ETag
ETag是如何生成的
ETag
生成结论
- 对于静态文件(如
css
、js
、图片等),ETag
的生成策略是:文件大小的16进制+修改时间 - 对于字符串或
Buffer
,ETag的生成策略是:字符串/Buffer长度的16进制+对应的hash
值
ETag 的作用
ETag
是一个不透明的标识符,由 Web
服务器根据 URL
上的资源的特定版本而指定。如果 URL
上的资源内容改变,一个新的不一样的 ETag
就会被生成。ETag
可以看成是资源的指纹,它们能够被快速地比较,以确定两个版本的资源是否相同。
需要注意的是 ETag 的比较只对同一个 URL 有意义 —— 不同
URL
上资源的ETag
值可能相同也可能不同。
ETag 的语法
ETag: W/"<etag_value>"
ETag: "<etag_value>"
- W/(可选):'W/'(大小写敏感) 表示使用弱验证器。弱验证器很容易生成,但不利于比较。
<etag_value>
:实体标签唯一地表示所请求的资源。它们是位于双引号之间的 ASCII 字符串(如2c-1799c10ab70
)。没有明确指定生成ETag
值的方法。
ETag 的使用
当一个 URL
被请求,Web
服务器会返回资源和其相应的 ETag
值,它会被放置在 HTTP
响应头的 ETag
字段中:
HTTP/1.1 200 OK
Content-Length: 44
Cache-Control: max-age=10
Content-Type: application/javascript; charset=utf-8
+ ETag: W/"2c-1799c10ab70"
然后,客户端可以决定是否缓存这个资源和它的 ETag
。以后,如果客户端想再次请求相同的 URL
,将会发送一个包含已保存的 ETag
和 If-None-Match
字段的请求。
GET /index.js HTTP/1.1
Host: localhost:3000
Connection: keep-alive
+ If-None-Match: W/"2c-1799c10ab70"
客户端请求之后,服务器可能会比较客户端的 ETag
和当前版本资源的 ETag。如果 ETag
值匹配,这就意味着资源没有改变,服务器便会发送回一个极短的响应,包含 HTTP “304 未修改” 的状态。304
状态码告诉客户端,它的缓存版本是最新的,可以直接使用它。
跨域原理与解决办法
跨域,指的是从一个域名去请求另外一个域名的资源,即跨域名请求。跨域时,浏览器不能执行其他域名网站的脚本,这是由浏览器的同源策略造成的,是浏览器施加的安全限制, 跨域限制访问,其实是浏览器的限制。
同源策略是浏览器最核心也最基本的安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源 ,这是一个用于隔离潜在恶意文件的重要安全机制.
所谓同源是指:协议,域名,端口相同,即两个资源具有相同的源。 只要三者之间有一个不同,就是跨域(跨源)
非同源限制
- 无法读取非同源网页的
Cookie
、LocalStorage
和IndexedDB
- 无法对非同源网页的
DOM
和JS对象
进行操作 - 无法向非同源地址发送
AJAX请求
实现跨域的解决方案(6种)
JSONP
CORS
http-proxy
nginx
websocket
- 跨站脚本API访问,如:
postMessage
、document.domain
等
JSONP
由于同源策略的限制,
AJAX
请求是不允许进行跨域请求的,但是在HTML
中,拥有src
和href
属性的标签是可以跨域请求外部资源的,如link
、script
、img
等,根据<script>
标签的特性,开发人员想到了一个解决跨域请求的方法,即JSONP
,全名JSON with padding
例如,存在如下的JSONP
接口
https://www.baidu.com/sugrec
?prod=pc
&wd=什么是JSONP
&cb=getData
该接口的特点是:你输入一个指定的函数名,然后服务器会根据函数名返回一串JS的函数调用格式的字符串
服务器返回的内容,无非就是 函数名 (参数) 的格式。如果前端有一个名为
getData
、形参是一个对象
的JS函数,意味着我们可以把服务器返回的数据看成是一段调用了一个函数名为getData、形参是一个对象的函数的JS代码
那怎么让服务器返回的数据变成一段JS代码呢?在HTML
页面里面编写JS代码——只需将JS代码用<script>
标签括起来就可以让代码在页面加载的时候就运行了。<script>
标签的src
属性是可以跨域请求外部资源的,如果我们将我们要访问的接口做为src属性的值,可以访问该跨域接口了。如果我们再提供一个全局的函数getData
,用来对接口返回的数据进行操作,那就实现了跨域请求了。
JSONP
的原理:
- 使用
script
标签发送请求,这个标签支持跨域访问 - 在
script
标签里面给服务器端传递一个callback
callback
的值对应到页面一定要定义一个全局函数
function jsonp({url, params, cb}) {
return new Promise((resolve, reject) => {
let scriptDom = document.createElement('script');
window[cb] = function (data) {
resolve(data);
document.body.removeChild(scriptDom);
};
params = {...params, cb}
let arrs = []
for (let key in params) {
arrs.push(`${key}=${params[key]}`);
}
scriptDom.src = `${url}?${arrs.join('&')}`;
document.body.appendChild(scriptDom);
})
};
调用处理
jsonp({
url: 'https://www.baidu.com/sugrec',
params: {prod: 'pc', wd: '什么是JSONP'},
cb: 'getData'
}).then(data => {
console.log(data);
});
CORS
同源策略默认阻止“跨域”获取资源。但是跨域资源共享CORS
给了web服务器这样的权限:服务器可以选择是否允许跨域请求访问到它们的资源。
跨域资源共享(CORS
)是一种机制, 它由一系列的HTTP头
组成,这些HTTP头决定浏览器是否阻止前端 JavaScript
代码获取跨域请求的响应,从而克服了AJAX
只能同源使用的限制。
跨域时,浏览器会让请求带上
Origin
请求头,表明请求来自哪个站点;
而服务器必须要让响应带上允许跨域访问的Access-Control-Allow-Origin
响应头,表明允许某个站点可以进行访问该服务器。
简单请求
浏览器将CORS
请求分成两类:
- 简单请求
- 非简单请求
(1)HTTP请求方法是以下三种之一:
·HEAD
·GET
·POST
(2)只包含简单HTTP请求头,即:
·Accept,
·Accept-Language,
·Content-Language,
·Content-Type并且值是
application/x-www-form-urlencoded,
multipart/form-data,
或者 text/plain之一的。
当请求满足上面的两个条件时,则该请求被视为简单请求,否则被视为非简单请求。
简单请求与非简单请求的最主要区别就是跨域请求是否需要发送预检请求(preflight request
)
在进行跨域请求时,如果是简单请求,则浏览器会在请求中增加一个Origin
请求头之后直接发送CORS请求,服务器检查该请求头的值是否在服务器设置的CORS
许可范围内,如果在许可范围内,则服务器同意本次请求,
如果不在许可范围内,则服务会返回一个没有包含
Access-Control-Allow-Origin
响应头的HTTP
响应。(最终的处理,是由浏览器决定是否用这个数据信息)
非简单请求
如果是非简单请求,则浏览器会先发起一次预检请求(OPTIONS
请求),浏览器除了会带上Origin
请求头之外,还会再带上Access-Control-Request-Method
和 Access-Control-Request-Headers
这两个请求头,服务器在收到预检请求之后,会检查这三个请求头是否与服务器的资源设置(接口)一致。
只要预检请求中三个请求头有任意一个值与服务器的资源(接口)设置不一致,服务器就会拒绝预检请求,如果都一致,则服务器确认通过预检请求并返回带有Access-Control-Allow-Credentials
、Access-Control-Allow-Headers
、Access-Control-Allow-Methods
、Access-Control-Allow-Origin
、Access-Control-Max-Age
、Allow
等响应头的相应
CORS settings attributes
在 HTML5
中,一些 HTML 元素提供了对 CORS
的支持,例如 <audio>
、<img>
、<link>
、<script>
和 <video>
均有一个{跨域属性|crossOrigin property} ,它允许你配置元素获取数据的 CORS
请求。
在非同源情况下,设置 anonymous
关键字将不会通过 cookies
,客户端 SSL
证书或 HTTP
认证交换用户凭据。
HTTP-Proxy
同源策略是浏览器施加的安全限制,它只存在于浏览器中。
因此,我们可以在前端服务器与后端服务器之间加一个代理中间件(比如Node中间件
)来实现,通过代理中间件转发请求,从而达到跨域请求的目的。
var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();
app.use('/api',
proxy({
target: 'http://localhost:3001/',
changeOrigin: true
})
);
app.listen(3000);
利用express
在3000端口启动了一个小型的服务器,利用了
app.use('/api', proxy({target: 'http://localhost:3001/', changeOrigin: true}))
;
这句话,使发到3000端口
的/api请求
转发到了3001端口
。即请求http://localhost:3000/api
相当于请求http://localhost:3001/api
Nginx
Nginx配置解决iconfont跨域
浏览器跨域访问js
、css
、img
等常规静态资源是被同源策略许可的,但iconfont
字体文件(eot
|otf
|ttf
|woff
|svg
)例外,这些文件是不会被允许的跨域访问的,此时可在Nginx
的静态资源服务器中加入以下配置
location ~* \.(eot|ttf|woff|svg|otf)$ {
add_header Access-Control-Allow-Origin *;
}
或者是:
location / {
add_header Access-Control-Allow-Origin *;
}
Nginx反向代理接口跨域
还可以通过Nginx
配置一个代理服务器来转发请求,反向代理访问后台接口,并修改cookie
中域名信息,从而实现跨域携带cookie
。
server {
listen 80;
server_name www.front.com;
location / {
#反向代理
proxy_pass http://www.end.com:8080;
#将cookie里的域名修改为前端的域名
proxy_cookie_domain www.end.com www.front.com;
index index.html index.htm;
# 如果不是浏览器直接访问Nginx时,下面的跨域配置可不启用,下面配置是为了添加响应头
add_header Access-Control-Allow-Origin http://www.front.com; #当前端只进行跨域不需要携带cookie时,可为*,否则不能为*,具体看后面附录补充的请求头的说明
add_header Access-Control-Allow-Credentials true;
}
}
WebSocket
浏览器允许脚本直连一个WebSocket
地址而不遵循同源策略,所以我们可以通过使用WebSocket
协议来实现跨域。具体代码如下:
var socket = io('http://www.front.com:8080');
socket.on('connect', function() {
socket.on('message', function(msg) {
console.log('data from server: ---> ' + msg);
});
socket.on('disconnect', function() {
console.log('Server socket has closed.');
});
});
document.getElementsByTagName('input')[0].onblur = function() {
socket.send(this.value);
};
验证码的原理和防范原因
验证码作为一种人机识别手段,其终极目的,就是区分正常人和机器的操作。而对于没有验证码的场景,比如用户登陆,则机器可以同时、大批量的发起请求,
- 一来系统可能扛不住
- 二来机器可以不断变换密码来尝试破解用户的密码,实现盗号。
工作原理
工作原理是:
- 使用
random
函数随机生成一个验证码,位数可以自己定(一般是4位、6位验证码) - 调用短信运营商的接口,并向其发送手机号和验证码数据。
- 短信运营平台初步审核将信息发送给三大运营商的某一家
- 运营商最终审核,将信息发送到用户手机上。
- 网站或app保存接口返回的信息
- 服务器端会将这些信息保存在会话控制器
Session
中,作为后期的验证使用 - 网站或app接收用户填写的验证码信息
- 服务器端判断用户提交的验证码是否与储存在会话控制器Session中的信息一致,如果一致就通过;否则就验证失败。(也可以同时验证信息的失效时间)
HTTPS
HTTPS
并不是一个新的应用层协议,它其实就是 HTTP + TLS/SSL
协议组合而成,而安全性的保证正是 TLS/SSL
所做的工作。
HTTPS 就是身披了一层
SSL
的HTTP
。
探讨 HTTPS
的握手过程,其实就是 SSL/TLS
的握手过程。
TLS
旨在为 Internet
提供通信安全的加密协议。TLS 握手是启动和使用 TLS 加密的通信会话的过程。在 TLS
握手期间,Internet
中的通信双方会彼此交换信息,验证密码套件,交换会话密钥。
每当用户通过 HTTPS
导航到具体的网站并发送请求时,就会进行 TLS
握手。除此之外,每当其他任何通信使用HTTPS
(包括 API 调用和在 HTTPS
上查询 DNS
)时,也会发生 TLS
握手。
TLS 具体的握手过程会根据所使用的密钥交换算法的类型和双方支持的密码套件而不同。
HTTPS握手过程
- 首先是
tcp
的三次握手建立连接 client
发送client-random
+支持的加密算法集合server
收到信息,返回选择一个加密算法
+server-random
+证书
client
验证证书有效性,并用client-random
+server-random
生成pre-master
通过服务器公钥加密 发送给server
server
收到premaster
,根据约定的加密算法对client-random+server-random+pre-master
(解密)生成master-secret
,然后发送预定成功client
收到生成同样的master-secert
,对称加密秘钥传输完毕
常见的 http状态码和请求头
HTTP 状态码分类
HTTP 状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型。响应分为五类:
- 信息响应(100–199)
- 成功响应(200–299)
- 重定向(300–399)
- 客户端错误(400–499)
- 服务器错误 (500–599)
消息头
消息头按照用途可分为四大类
- 通用头:适用于请求和响应的头字段
- 请求头:用于表示请求消息的附加信息的头字段
- 响应头:用于表示响应消息的附加信息的头字段
- 实体头:用于消息体的附加信息的头字段
请求头
用户登录是如何实现的
- 基于
server
端session
的管理方式 cookie-base
的管理方式token-base
的管理方式- JWT
基于server端session的管理
- 服务端
session
是用户第一次访问应用时,服务器就会创建的对象,代表用户的一次会话过程,可以用来存放数据。服务器为每一个session都分配一个唯一的sessionid,以保证每个用户都有一个不同的session对象。 - 服务器在创建完
session
后,会把sessionid通过cookie返回给用户所在的浏览器,这样当用户第二次及以后向服务器发送请求的时候,就会通过cookie把sessionid传回给服务器,以便服务器能够根据sessionid
找到与该用户对应的session
对象。 session
通常有失效时间的设定,比如2个小时。当失效时间到,服务器会销毁之前的session
,并创建新的session
返回给用户。但是只要用户在失效时间内,有发送新的请求给服务器,通常服务器都会把他对应的session
的失效时间根据当前的请求时间再延长2个小时。session
在一开始并不具备会话管理的作用。它只有在用户登录认证成功之后,并且往sesssion对象
里面放入了用户登录成功的凭证,才能用来管理会话。管理会话的逻辑也很简单,只要拿到用户的session
对象,看它里面有没有登录成功的凭证,就能判断这个用户是否已经登录。当用户主动退出的时候,会把它的session对象里的登录凭证清掉。所以在用户登录前或退出后或者session
对象失效时,肯定都是拿不到需要的登录凭证的。
存在问题
- 这种方式将会话信息存储在web服务器里面,所以在用户同时在线量比较多时,这些会话信息会占据比较多的内存;
- 当应用采用集群部署的时候,会遇到多台web服务器之间如何做session共享的问题
- 多个应用要共享
session
时,还会遇到跨域问题,因为不同的应用可能部署的主机不一样,需要在各个应用做好cookie跨域的处理。
cookie-base
的管理方式
- 用户发起登录请求,服务端根据传入的用户密码之类的身份信息,验证用户是否满足登录条件,如果满足,就根据用户信息创建一个登录凭证,这个登录凭证简单来说就是一个对象,最简单的形式可以只包含用户id,凭证创建时间和过期时间三个值。
- 服务端把上一步创建好的登录凭证,先对它做数字签名,然后再用对称加密算法做加密处理,将签名、加密后的字串,写入cookie。
cookie
的名字必须固定(如ticket
),因为后面再获取的时候,还得根据这个名字来获取cookie
值。这一步添加数字签名的目的是防止登录凭证里的信息被篡改,因为一旦信息被篡改,那么下一步做签名验证的时候肯定会失败。做加密的目的,是防止cookie
被别人截取的时候,无法轻易读到其中的用户信息。 - 用户登录后发起后续请求,服务端根据上一步存登录凭证的
cookie
名字,获取到相关的cookie
值。然后先做解密处理,再做数字签名的认证,如果这两步都失败,说明这个登录凭证非法;如果这两步成功,接着就可以拿到原始存入的登录凭证了。然后用这个凭证的过期时间和当前时间做对比,判断凭证是否过期,如果过期,就需要用户再重新登录;如果未过期,则允许请求继续。
存在问题
cookie
有大小限制,存储不了太多数据- 每次传送
cookie
,增加了请求的数量,对访问性能也有影响; - 跨域问题
前面两种会话管理方式因为都用到
cookie
,不适合用在native app
里面:native app
不好管理cookie
,毕竟它不是浏览器。这两种方案都不适合用来做纯api服务的登录认证
token-based的管理方式
跟cookie-based
的方式没有太多区别,只不过cookie-based
里面写到cookie
里面的ticket
在这种方式下称为token
,这个token
在返回给客户端之后,后续请求都必须通过url参数或者是http header的形式,主动带上token,这样服务端接收到请求之后就能直接从http header
或者url
里面取到token进行验证:
JWT
JSON Web Token
(缩写JWT
)是目前最流行的跨域认证解决方案
JWT 的原理
JWT 的原理是,服务器认证以后,生成一个 JSON
对象,发回给用户。
以后,用户与服务端通信的时候,都要发回这个 JSON
对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
服务器就不保存任何 session
数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
JWT 的数据结构
它是一个很长的字符串,中间用点(.)分隔成三个部分 JWT 的三个部分依次如下。
Header
(头部)Header
部分是一个JSON
对象,描述JWT
的元数据- 将上面的
JSON
对象使用Base64URL
算法转成字符串。
Payload
(负载)Payload
部分也是一个JSON
对象,用来存放实际需要传递的数据- 这个
JSON
对象也要使用Base64URL
算法转成字符串。
Signature
(签名)
JWT 的使用方式
客户端收到服务器返回的 JWT
,可以储存在 Cookie
里面,也可以储存在 localStorage
。
此后,客户端每次与服务器通信,都要带上这个 JWT
。你可以把它放在 Cookie
里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP
请求的头信息Authorization
字段里面。
Authorization: Bearer <token>
前端安全知识
- {跨站脚本攻击|Cross Site Scripting}
- 允许攻击者将恶意代码植入到提供给其它用户使用的页面中。
- 原理:攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的
Script
代码会被执行,从而达到恶意攻击用户的目的。 - 反射型XSS:攻击者事先制作好攻击链接, 需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般容易出现在搜索页面
- 存储型XSS:代码是存储在服务器中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,每当有用户访问该页面的时候都会触发代码执行,这种XSS非常危险,容易造成蠕虫,大量盗窃cookie(虽然还有种DOM型XSS,但是也还是包括在存储型XSS内)。
- DOM型XSS
- {跨站请求伪造|Cross-site Request Forgery}
- 攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。
- 防御措施:1. 阻止不明外域的访问(同源检测) 2.
CSRF Token
/双重Cookie验证
- SQL注入
- 通过将恶意的 Sql查询或添加语句插入到应用的输入参数中,再在后台 Sql服务器上解析执行进行的攻击。
- {分布式拒绝服务攻击|Distributed Denial of Service}
- 拒绝服务攻击的升级版。拒绝攻击服务顾名思义,让服务不可用
- 攻击者不断地提出服务请求,让合法用户的请求无法及时处理,这就是 DoS 攻击。
- 攻击者使用多台计算机或者计算机集群进行 DoS 攻击,就是 DDoS 攻击。
- DNS劫持
- 为了在客户端对收到对DNS应答进行校验,出现了
DNSSEC
技术 - 以阿里、腾讯等头部互联网厂商开始推出了
httpDNS
服务
- 为了在客户端对收到对DNS应答进行校验,出现了
- JSON 劫持
HTTP X.X
HTTP/0.9
HTTP/0.9 是于 1991
年提出的,主要用于学术交流,需求很简单——用来在网络之间传递 HTML
超文本的内容,所以被称为超文本传输协议。
HTTP/0.9 的一个完整的请求流程
- 因为
HTTP
都是基于TCP
协议的,所以客户端先要根据 IP 地址、端口和服务器建立 TCP 连接,而建立连接的过程就是 TCP 协议三次握手的过程。 - 建立好连接之后,会发送一个
GET
请求行的信息,如GET /index.html
用来获取 index.html。 - 服务器接收请求信息之后,读取对应的 HTML 文件,并将数据以 ASCII 字符流返回给客户端。
- HTML 文档传输完成后,断开连接。
总的来说,当时的需求很简单,就是用来传输体积很小的 HTML 文件,所以 HTTP/0.9 的实现有以下三个特点。
- 第一个是只有一个请求行,并没有 HTTP 请求头和请求体,因为只需要一个请求行就可以完整表达客户端的需求了。
- 第二个是服务器也没有返回头信息,这是因为服务器端并不需要告诉客户端太多信息,只需要返回数据就可以了。
- 第三个是返回的文件内容是以 ASCII 字符流来传输的,因为都是 HTML 格式的文件,所以使用 ASCII 字节码来传输是最合适的。
HTTP 1.0
HTTP 1.0 是在 1996
年引入的,由于在浏览器中展示的不单是 HTML 文件了,还包括了 JavaScript
、CSS
、图片、音频、视频等不同类型的文件。因此支持多种类型的文件下载是 HTTP/1.0 的一个核心诉求。
- HTTP 1.0 仅仅提供了最基本的认证,这时候用户名和密码还未经加密,因此很容易收到窥探。
- HTTP/1.0 引入了请求头和响应头,它们都是以为
Key-Value
形式保存的 - HTTP 1.0 被设计用来使用短链接
- 即每次发送数据都会经过 TCP 的三次握手和四次挥手,效率比较低。
- HTTP 1.0 只使用
header
中的If-Modified-Since
和Expires
作为缓存失效的标准。 - HTTP 1.0 不支持断点续传,也就是说,每次都会传送全部的页面和数据。
HTTP 1.1
HTTP 1.1 是 HTTP 1.0 开发三年后出现的,也就是 1999
年,它做出了以下方面的变化
- HTTP 1.1 使用了摘要算法来进行身份验证
- HTTP 1.1 默认使用长连接(持久连接)
- 长连接就是只需一次建立就可以传输多次数据,传输完成后,只需要一次切断连接即可。
- 长连接的连接时长可以通过请求头中的
keep-alive
来设置 - 持久连接在 HTTP/1.1 中是默认开启的
- HTTP 1.1 中新增加了
E-tag
,If-Match
,If-None-Match
等缓存控制标头来控制缓存失效。 - HTTP 1.1 支持断点续传,通过使用请求头中的
Range
来实现。
HTTP 2.0
HTTP 2.0 是 2015
年开发出来的标准,HTTP/2
的一个核心特性是使用了 多路复用技术,因此它可以 通过一个 TCP 连接来发送多个 URL 请求。多路复用技术能充分利用带宽,最大限度规避了 TCP 的慢启动所带来的问题。
- 头部压缩
- 由于 HTTP 1.1 经常会出现
User-Agent
、Cookie
、Accept
、Server
、Range
等字段可能会占用几百甚至几千字节,而Body
却经常只有几十字节,所以导致头部偏重。 - HTTP 2.0 使用
HPACK
算法进行压缩。
- 由于 HTTP 1.1 经常会出现
- 二进制格式
- HTTP 2.0 使用了更加靠近 TCP/IP 的二进制格式
- 而抛弃了 ASCII 码,提升了解析效率
- 强化安全
- HTTP2.0 一般都跑在
HTTPS
上。
- HTTP2.0 一般都跑在
- 多路复用
- 一个域名只使用一个 TCP 长连接来传输数据
- 一个请求对应一个id,这样一个连接上可以有多个请求。
- 通过引入二进制分帧层,就实现了 HTTP 的多路复用技术
- 可以设置请求的优先级
- 服务器推送
HTTP 3.0
HTTP/3 选择了一个折衷的方法——UDP
协议,基于 UDP
实现了类似于 TCP 的多路数据流、传输可靠性等功能,我们把这套功能称为 QUIC 协议。
- 实现了类似
TCP
的流量控制、传输可靠性的功能。虽然UDP
不提供可靠性的传输,但QUIC
在UDP
的基础之上增加了一层来保证数据可靠性传输。它提供了数据包重传、拥塞控制以及其他一些TCP
中存在的特性。 - 集成了
TLS
加密功能。目前QUIC
使用的是TLS1.3
,相较于早期版本TLS1.2
有更多的优点,其中最重要的一点是减少了握手所花费的 RTT 个数。 - 实现了
HTTP/2
中的多路复用功能。和TCP
不同,QUIC
实现了在同一物理连接上可以有多个独立的逻辑数据流(如下图)。实现了数据流的单独传输,就解决了TCP
中的问题。 - 实现了快速握手功能。由于
QUIC
是基于UDP
的,所以QUIC
可以实现使用0-RTT
或者1-RTT
来建立连接,这意味着QUIC
可以用最快的速度来发送和接收数据,这样可以大大提升首次打开页面的速度。
WebSocket协议
HTML5
开始提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议
。它基于TCP传输协议,并复用HTTP的握手通道。
WebSocket连接是如何创建的
在客户端的使用
var url = "ws://localhost:8080/websocket/text";
var ws = new WebSocket(url);
ws.onopen = function(event) {
console.log("websocket connection open.");
console.log(event);
};
ws.onmessage = function(event) {
console.log("websocket message received.")
console.log(event.data);
};
ws.onclose = function (event) {
console.log("websocket connection close.");
console.log(event.code);
};
ws.onerror = function(event) {
console.log("websocket connection error.");
console.log(event);
};
readyState(4个)
返回当前 WebSocket
的链接状态,只读。
let readyState = socket.readyState;
返回值
- 0 (
WebSocket.CONNECTING
)- 正在链接中
- 1 (
WebSocket.OPEN
)- 已经链接并且可以通讯
- 2 (
WebSocket.CLOSING
)- 连接正在关闭
- 3 (
WebSocket.CLOSED
)- 连接已关闭或者没有链接成功
首先,WebSocket
连接必须由浏览器发起,因为请求协议是一个标准的HTTP
请求
+GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
+Upgrade: websocket
+Connection: Upgrade
Origin: http://localhost:3000
+Sec-WebSocket-Key: client-random-string
+Sec-WebSocket-Version: 13
该请求和普通的HTTP请求有几点不同:
- GET请求的地址不是类似
/path/
,而是以ws://
开头的地址;- 请求头
Upgrade: websocket
和Connection: Upgrade
表示这个连接将要被转换为WebSocket连接;Sec-WebSocket-Key
是用于标识这个连接,并非用于加密数据;Sec-WebSocket-Version
指定了WebSocket的协议版本。
随后,服务器如果接受该请求,就会返回如下响应:
+HTTP/1.1 101 Switching Protocols
+Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string
该响应代码101
表示本次连接的HTTP协议即将被更改,更改后的协议就是Upgrade: websocket
指定的WebSocket协议。
WebSocket 优点
- 较少的控制开销
- 在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。
- 更强的实时性
- 由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少。
- 保持连接状态
- 与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。
- 更好的二进制支持。
- WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容。
- 可以支持扩展。
- WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。
DNS协议:网络世界的地址簿
Socket 库提供查询 IP 地址的功能
向 DNS
服务器发出查询,并接收服务器返回的响应消息。对于 DNS
服务器,我们的计算机上一定有相应的 DNS 客户端,而相当于 DNS 客户端的部分称为 DNS 解析器
,或者简称解析器。
通过 DNS 查询 IP 地址的操作称为域名解析
解析器实际上是一段程序,它包含在操作系统的 Socket
库中。
解析器向 DNS 服务器发出查询
调用解析器后,解析器会向 DNS 服务器发送查询消息,然后 DNS 服务器会返回响应消息。响应消息中包含查询到的 IP 地址,解析器会取出 IP 地址,并将其写入浏览器指定的内存地址中。
接下来,浏览器在向 Web 服务器发送消息时,只要从该内存地址取出 IP 地址,并将它与 HTTP 请求消息一起交给操作系统就可以了。
在 Socket 库中,采用
UDP
协议,进行信息的查询。
DNS 服务器的工作步骤
DNS 服务器的基本工作就是接收来自客户端的查询消息,然后根据消息的内容返回响应。
来自客户端的查询消息包含以下 3 种信息。
类型 | 描述 |
---|---|
域名 | 服务器、邮件服务器(邮件地址中 @ 后面的部分)的名称 |
Class | Class 的值永远是代表互联网的 IN |
记录类型 | 表示域名对应何种类型的记录 类型为 A 时:表示域名对应的是 IP 地址 类型为 MX 时:表示域名对应的是邮件服务器 |
A 是 Address 的缩写 /MX:Mail eXchange,邮件交换的缩写
DNS 服务器上事先保存有前面这 3 种信息对应的记录数据。
例如,如果要查询 www.wl.com
这个域名对应的 IP 地址,客
户端会向 DNS 服务器发送包含以下信息的查询消息。
信息 |
---|
域名 = www.wl.com |
Class = IN |
记录类型 = A |
然后,DNS 服务器会从已有的记录中查找域名、Class 和记录类型全部匹配的记录。
DNS 服务器会从域名与 IP 地址的对照表中查找相应的记录,并返回 IP 地址
实际上还有很多其他的类型。
- 根据 IP 地址反查域名的
PTR
类型 - 查询域名相关别名的
CNAME
类型 - 查询 DNS 服务器 IP 地址的
NS
类型 - 以及查询域名属性信息的
SOA
类型等
信息是如何在 DNS 服务器上注册
首先,DNS 服务器中的所有信息都是按照域名以分层次的结构来保存的。DNS 中的域名都是用句点来分隔的,比如 www.wl.com
,这里的句点代表了不同层次之间的界限。
在域名中,越靠右的位置表示其层级越高。
这种具有层次结构的域名信息会注册到 DNS 服务器中,而每个域都是作为一个整体来处理的。
于是,DNS 服务器也具有了像域名一样的层次结构,每个域的信息都存放在相应层级的 DNS 服务器中。
寻找相应的 DNS 服务器并获取 IP 地址
这里的关键在于如何找到我们要访问的 Web 服务器的信息归哪一台 DNS 服务器管。
com
、cn
这些域(称为顶级域),它们各自负责保存下级 DNS 服务器的信息。在互联网中,com
和 cn
的上面还有一级域,称为根域。根域不像 com、cn 那样有自己的名字,因此在一般书写域名时经常被省略,如果要明确表示根域,应该像 www.wl.com.
这样在域名的最后再加上一个句点,而这个最后的句点就代表根域。
根域的 DNS 服务器中保管着
com
、cn
等的 DNS 服务器的信息
除此之外还需要完成另一项工作,那就是将根域的 DNS 服务器信息保存在互联网中所有的 DNS 服务器中。客户端只要能够找到任意一台DNS 服务器,就可以通过它找到根域 DNS 服务器,然后再一路顺藤摸瓜找到位于下层的某台目标 DNS 服务器。
分配给根域 DNS 服务器的 IP 地址在全世界仅有 13 个 ,而且这些地址几乎不发生变化。
DNS 解析流程
-
电脑客户端会发出一个 DNS 请求,问
www.wl.com
的 IP 是啥啊,并发给本地域名服务器 (本地 DNS)。 -
本地 DNS 收到来自客户端的请求。然后,查找对应的记录信息。
- 如果能找到
www.wl.com
,它直接就返回 IP 地址。 - 如果没有,本地 DNS 会去问它的根域名服务器。
- 如果能找到
-
根 DNS 收到来自本地 DNS 的请求,发现后缀是
.com
,说:“www.wl.com 啊,这个域名是由.com 区域管理,我给你它的顶级域名服务器的地址,你去问问它吧。” -
本地 DNS 转向问顶级域名服务器:
- 顶级域名服务器就是大名鼎鼎的比如
.com、.net、 .org
这些一级域名 - 它负责管理二级域名,比如
wl.com
,所以它能提供一条更清晰的方向
- 顶级域名服务器就是大名鼎鼎的比如
-
顶级域名服务器说:“我给你负责 www.wl.com 区域的权威 DNS 服务器的地址,你去问它应该能问到。”
-
本地 DNS 转向问权威 DNS 服务器:“www.wl.com 对应的 IP 是啥呀?”
- wl.com 的权威 DNS 服务器,它是域名解析结果的原出处。
- 为啥叫权威呢?就是我的域名我做主。
-
权威 DNS 服务器查询后将对应的 IP 地址 X.X.X.X 告诉本地 DNS。
-
本地 DNS 再将 IP 地址返回客户端,客户端和目标建立连接。
通过缓存加快 DNS 服务器的响应
如果要查询的域名和相关信息已经在缓存中,那么就可以直接返回响应,接下来的查询可以从缓存的位置开始向下进行。相比每次都从根域找起来说,缓存可以减少查询所需的时间。
这个缓存机制中有一点需要注意,那就是信息被缓存后,原本的注册信息可能会发生改变,这时缓存中的信息就有可能是不正确的。因此,DNS 服务器中保存的信息都设置有一个有效期,当缓存中的信息超过有效期后,数据就会从缓存中删除。
负载均衡
内部负载均衡
例如,某个应用要访问另外一个应用,如果配置另外一个应用的 IP 地址,那么这个访问就是一对一的。但是当被访问的应用撑不住的时候,我们其实可以部署多个。但是,访问它的应用,如何在多个之间进行负载均衡?只要配置成为域名就可以了。在域名解析的时候,我们只要配置策略,这次返回第一个 IP,下次返回第二个 IP,就可以实现负载均衡了。
全局负载均衡
为了保证我们的应用高可用,往往会部署在多个机房,每个地方都会有自己的 IP 地址。当用户访问某个域名的时候,这个 IP 地址可以轮询访问多个数据中心。如果一个数据中心因为某种原因挂了,只要在 DNS 服务器里面,将这个数据中心对应的 IP 地址删除,就可以实现一定的高可用
TCP
三次握手
我们来简单介绍一下,重要字段的作用。
- {源端口号|Source Port}/ {目标端口号|Destination Port}:
- 用于区别主机中的不同进程
- IP地址是用来区分不同的主机的
- 源端口号和目的端口号配合上IP首部中的源IP地址和目的IP地址就能唯一的确定一个TCP连接;
- {分包序号|Sequence Number}:
- 应用程序数据如果 大于 {最大分段大小|Maximum Segment Size}(
MSS
)就得要进行分段。 - 这个
Sequence Number
就是记录每个封包的序号,可以让 server 重新将 TCP 的数据组合起来。 -
主要用来解决网络报乱序的问题
- 应用程序数据如果 大于 {最大分段大小|Maximum Segment Size}(
- {回应序号|Acknowledge Number}:
- 为了确认发送端确实有收到接收端所送出的封包数据。
- 当 接收端收到这个确认码时,就能够确定之前传递的封包已经被正确的收下了。
- 回应序号应当是上次已成功收到分包序号加1。
-
用来解决不丢包的问题
- Code({控制标识码|Control Flag})
这个字段共有 6 个 bits ,分别代表 6 个句柄,若为 1 则为启动 (只介绍常用的)- SYN:若为 1,表示 client 希望双方建立同步处理
- ACK:若为 1 代表这个封包为响应封包
- FIN:若为 1 ,表示传送结束,所以通知对方数据传毕, 是否同意断线,只是发送者还在等待对方的响应而已
- SYN
- 客户端选择一个随机序列号
x
,并发送一个SYN
分组
- 客户端选择一个随机序列号
- SYN + ACK
- 服务器给 x 加 1
x + 1
- 并选择自己的一个随机序列号
y
- 然后返回响应
- 服务器给 x 加 1
- ACK
- 客户端给 x 和 y 加 1
x + 1
y + 1
- 并发送握手期间的最后一个 ACK 分组
- 客户端给 x 和 y 加 1
三次握手完成后,客户端与服务器之间就可以通信了。客户端可以在发送 ACK 分组之后立即发送数据,而服务器必须等接收到 ACK 分组之后才能发送数据。
滑动窗口
TCP 采用滑动窗口来管理数据发送和 ACK 号的操作。
所谓滑动窗口,就是在发送一个包之后,不等待 ACK 号返回,而是直接发送后续的一系列包
通过这种方式,就可以实现同一时间发送多个包,减少网络延迟。
其实,通过窗口,TCP 可以控制双向发送数据的速度。
流量控制
流量控制是一种预防发送端过多向接收端发送数据的机制。
为实现流量控制,TCP 连接的每一方都要通告自己的接收窗口(rwnd
),其中包含能够保存数据的缓冲区空间大小信息。
第一次建立连接时,两端都会使用自身系统的默认设置来发送 rwnd
。在后面的数据交换过程中,每个 ACK 分组都会携带相应的最新 rwnd
值,以便两端动态调整数据流速,使之适应发送端和接收端的容量及处理能力。
慢启动 (利用可用宽带)
流量控制确实可以防止发送端向接收端过多发送数据,但却没有机制预防任何一端向潜在网络过多发送数据。换句话说,发送端和接收端在连接建立之初,谁也不知道可用带宽是多少。因此需要一个估算机制,然后还要根据网络中不断变化的条件而动态改变速度。
拥塞窗口大小(cwnd
):发送端对从客户端接收确认(ACK)之前可以发送数据量的限制。发送端不会通告 cwnd
变量,即发送端和接收端不会交换这个值。
服务器和客户端怎么确定拥塞窗口大小的最优值呢:解决方案就是慢启动:即在分组被确认后增大窗口大小,慢慢地启动。
无论带宽多大,每个 TCP 连接都必须经过慢启动阶段
换句话说,应用不可能一上来就完全利用连接的最大带宽
把初始拥塞窗口大小增加到一个合理值,可以减少客户端与服务器之间的往返时间
TCP队首阻塞
TCP 在不可靠的信道上实现了可靠的网络传输
每个 TCP 分组都会带着一个唯一的序列号被发出,而所有分组必须按顺序传送到接收端。如果中途有一个分组没能到达接收端,那么后续分组必须保存在接收端的 TCP 缓冲区,等待丢失的分组重发并到达接收端。这一切都发生在 TCP 层,应用程序对 TCP 重发和缓冲区中排队的分组一无所知,必须等待分组全部到达才能访问数据。在此之前,应用程序只能在通过套接字读数据时感觉到延迟交付。这种效应称为TCP {队首阻塞|Head of Line Blocking} (HOL)
队首阻塞造成的延迟可以让我们的应用程序不用关心分组重排和重组,分组到达时间会存在无法预知的延迟变化。这个时间变化通常被称为抖动,也是影响应用程序性能的一个主要因素。
TCP 队首阻塞造成的延迟,也是影响应用程序性能的一个主要因素
四次挥手
- FIN
- 客户端选择一个随机序列号
x
,并发送一个FIN
分组
- 客户端选择一个随机序列号
- ACK
- 服务器给 x 加 1
x + 1
- 服务器给 x 加 1
- 等后端数据都传输完毕后。。。。。
- FIN
- 服务器选择自己的一个随机序列号
y
- 服务器选择自己的一个随机序列号
- ACK
- 客户端给 x 和 y 加 1
x + 1
y + 1
- 并发送握手期间的最后一个 ACK 分组
- 客户端给 x 和 y 加 1
注意点:
- 相比三次握手,四次挥手,在 server 发起的时候,是将控制标志码由
SYN
换成FIN
。 - 可以看到,在第二次挥手和第三次挥手中间,有很多未发送完成的数据,其实也好理解,在 client 接收到 server 传入的
FIN
包时候,此时可能正处于某些大包数据的发送阶段,如果此时直接回复 发送端的断开操作。并且,如果 server FIN 包早于其他正常数据包到达 client。那这些本应该被 client 收录的数据,就会平白无故的丢失。
为什么要四次挥手
TCP协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。
TCP是全双工模式,这就意味着
- 当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据
- 当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的
- 当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接
后记
分享是一种态度。
全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。