单点登录
同域 SSO
如图,同域 SSO 是最简单的一种情况。
此时,两个产品都是在一个域名下,单点登录是很自然的选择。
- 用户访问产品 a,向 后台服务器发送登录请求。
- 登录认证成功,服务器把用户的登录信息写入 session。服务器为该用户生成一个 cookie,并加入到 response header 中,随着请求返回而写入浏览器。该 cookie 的域设定为 dxy.cn。
- 下一次,当用户访问同域名的产品 b 时,由于 a 和 b 在同一域名下,也是 dxy.cn,浏览器会自动带上之前的 cookie。此时后台服务器就可以通过该 cookie 来验证登录状态了。
同父域 SSO
同父域 SSO 是同域 SSO 的简单升级,唯一的不同在于,服务器在返回 cookie 的时候,要把cookie 的 domain 设置为其父域。
比如两个产品的地址分别为 a.dxy.cn 和 b.dxy.cn,那么 cookie 的域设置为 dxy.cn 即可。在访问 a 和 b 时,这个 cookie 都能发送到服务器,本质上和同域 SSO 没有区别。
跨域 SSO
可以看到,在上面两种情况下,我们都没有专门设置 SSO 服务器。但是当两个产品不同域时,cookie 无法共享,所以我们必须设置独立的 SSO 服务器了。这个时候,我们就是通过标准的 CAS 方案来实现 SSO 的。下面我们就来详细介绍一下:
详解CAS
票据
-
TGT:TGT 是 CAS 为用户签发的登录票据,拥有了 TGT,用户就可以证明自己在 CAS 成功登录过。TGT 封装了 Cookie 值以及此 Cookie 值对应的用户信息。当 HTTP 请求到来时,CAS 以此 Cookie 值(TGC)为 key 查询缓存中有无 TGT ,如果有的话,则相信用户已登录过。
-
TGC:CAS Server登录成功生成TGT放入自己的 Session 中,而 TGC 就是这个 Session 的唯一标识(SessionId),以 Cookie 形式放到浏览器端,是 CAS Server 用来明确用户身份的凭证。
-
ST:CAS 为用户签发的访问某一 service 的票据。用户访问 service 时,service 发现用户没有 ST,则要求用户去 CAS 获取 ST。用户向 CAS 发出获取 ST 的请求,CAS 发现用户有 TGT,则签发一个 ST,返回给用户。用户拿着 ST 去访问 service,service 拿 ST 去 CAS 验证,验证通过后,允许用户访问资源。
为什么CAS登录成功后跳转原业务系统地址,业务系统还有拿着ST去CAS验证是否登陆过
如果我在SSO没有登录,而是直接在浏览器中敲入回调的地址,并带上伪造的用户信息,是不是业务系统也认为登录了呢?这是很可怕的
具体步骤
- 1、用户访问产品 a,域名是 www.a.cn。
- 2、由于用户没有携带在a服务器上登录的a cookie,所以 a 服务器返回 http 重定向,重定向的 url 是 SSO 服务器的地址,同时 url 的 query 中通过参数指明登录成功后,回跳到 a 页面。重定向的url 形如 sso.dxy.cn/login?service=https%3A%2F%2Fwww.a.cn
- 3、由于用户没有携带在 SSO 服务器上登录的 TGC(看上面,票据之一),所以 SSO 服务器判断用户未登录,给用户显示统一登录界面。用户在 SSO 的页面上进行登录操作。
- 4、登录成功后,SSO 服务器构建用户在 SSO 登录的 TGT(又一个票据),同时返回一个 http 重定向。这里注意:
- 重定向地址为之前写在 query 里的 a 页面
- 重定向地址的 query 中包含 sso 服务器派发的 ST。
- 重定向的 http response 中包含写 cookie 的 header。这个 cookie 代表用户在 SSO 中的登录状态,它的值就是 TGC
- 5、浏览器重定向到产品 a。此时重定向的 url 中携带着 SSO 服务器生成的 ST。
- 6、根据 ST,a 服务器向 SSO 服务器发送请求,SSO 服务器验证票据的有效性。验证成功后,a 服务器知道用户已经在 sso 登录了,于是 a 服务器构建用户登录 session,记为 a session。并将 cookie 写入浏览器。注意,此处的 cookie 和 session 保存的是用户在 a 服务器的登录状态,和 CAS 无关。
- 7、之后用户访问产品 b,域名是 www.b.cn。
- 8、由于用户没有携带在 b 服务器上登录的 b cookie,所以 b 服务器返回 http 重定向,重定向的 url 是 SSO 服务器的地址,去询问用户在 SSO 中的登录状态。
- 9、浏览器重定向到 SSO。注意,第 4 步中已经向浏览器写入了携带 TGC 的cookie,所以此时 SSO 服务器可以拿到,根据 TGC 去查找 TGT,如果找到,就判断用户已经在 sso 登录过了。
- 10、SSO 服务器返回一个重定向,重定向携带 ST。注意,这里的 ST 和第4步中的 ST 是不一样的,事实上,每次生成的 ST 都是不一样的。
- 11、浏览器带 ST 重定向到 b 服务器,和第 5 步一样。
- 12、b 服务器根据票据向 SSO 服务器发送请求,票据验证通过后,b 服务器知道用户已经在 sso 登录了,于是生成 b session,向浏览器写入 b cookie。
如图所示,至此,整个登录流程结束。之后当用户访问 a 或者 b 后,直接会携带 a cookie/b cookie,就不用再向 SSO 确认了。实际开发时,可以根据 CAS 增加更多的判断逻辑,比如,在收到CAS Server签发的ST后,如果 ST 被 hacker 窃取,并且 client 本身没来得及去验证 ST,被 hacker 抢先一步验证 ST,怎么解决。此时就可以在申请 ST 时添加额外验证因子(如ip、sessionId等)
为什么sso跳转到原系统后,还要带着ST去验证
其实这样问题时很严重的,如果我在SSO没有登录,而是直接在浏览器中敲入回调的地址,并带上伪造的用户信息,是不是业务系统也认为登录了呢?这是很可怕的
参考链接
什么是三方cookie? samesite、samteparty
背景
HTTP 协议是无状态的协议,如果你在同一个客户端向服务器发送多次请求,服务器不会知道这些请求来自同一客户端,如果你要为广大用户提供更好的服务,服务器就需要知道每个请求具体来自于哪个用户,比如你在逛某些购物网站的时候你只需要登录一次,当后续在该网站进行某些操作,服务器就已经知道你登录过了,不会再让你进行登录。
所以 HTTP 协议需要占用浏览器的一小块存储,存储当前访问用户的一些 ”状态“,然后每次发起 HTTP 请求,请求中就会携带这些状态,从而让服务器知道你是谁。Cookie 出现的的意义就是为了解决这个问题,让无状态的 HTTP 协议拥有一小块记忆。
但是, Cookie 一经出现,就成了各大广告和购物网站窥探用户隐私的利器,他们使用第三方 Cookie 不断获取你的数据,那么什么第三方 Cookie 呢
第三方cookie
假设我们正常浏览今日头条网页,头条会把你的信息写入一些 Cookie 到 .toutiao.com 这个域下,然而打开控制台你会看到,并不是所有 Cookie 都是 .toutiao.com 这个域下的,里面还有很多其他域下的 Cookie ,这些非当域下的Cookie都属于第三方Cookie
三方cookie用途
- 前端日志上报:
- 多数SDK都需要标识每个用户来方便排查问题或者统计
UV数据 - 多数SDK服务会在使用站点Set一个cookie,后续每次日志上报都会携带该cookie
- 多数SDK都需要标识每个用户来方便排查问题或者统计
- 广告服务 精准推送广告
Cookie属性
- name/value
- max-age
- 为正数cookie失效经历的毫秒数。
- 为负数: 标识这是一个会话行的cookie
- 为0, 删除该cookie
- Expries
max-age和expries在一起的话,max-age优先级更高- 过期时间
- path
- domain
- httpOnly
- 通过document.cookie读取不到
- secure
- 标记为 Secure 的 Cookie 只应通过被HTTPS协议加密过的请求发送给服务端。使用 HTTPS 安全协议,可以保护 Cookie 在浏览器和 Web 服务器间的传输过程中不被窃取和篡改
- samesite
- sameparty
网站可以选择显式关闭SameSite属性,将其设为None。 不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效
samesite
SameSite 属性可以让 Cookie 在跨站请求时不会被发送,从而可以阻止跨站请求伪造攻击(CSRF)。
跨域: 验证 协议、域名、端口 不一致就跨域
跨站: 验证相对跨域比较请求, 即验证顶级域名(.com、.co.uk、.github.io)+二级域名
举几个例子,www.toutiao.com 和 www.baidu.com 是跨站,www.a.toutiao.com 和 www.b.toutiao.com 是同站,
a.github.io 和 b.github.io 是跨站(注意是跨站)。
- strict: 完全禁止第三方cookie, 可有有效的防止CSRF
- lax: 允许部分第三方请求携带cookie
- none: 无论是否跨站都会发送cookie
sameparty
在上面正常的业务场景中,所有不同的域名基本上都来自同一个组织或企业,我们希望在同一个运营主体下不同域名的 Cookie 也能共享。
First-Party Sets 可以定义跨站点上下文仍然是 first-party 的情况。 Cookie 可以包含在第一方集合中,也可以排除在第三方上下文中
- sameparty Cookie 必须包含 secure.
- sameparty Cookie 不得包含 sameSite=Strict.
注意点
- http协议下不支持cookie设置 samesite:none
- IOS 12 的 Safari 以及老版本的一些 Chrome 会把 SameSite=none 识别成 SameSite=Strict,所以服务端必须在下发 Set-Cookie 响应头时进行 User-Agent 检测,对这些浏览器不下发 SameSite=none 属性
参考文章
http 协商缓存 & 强制缓存
强缓存是利用http头部中的cache-control和expries控制
浏览器缓存(Brower Caching)是浏览器对之前请求过的文件进行缓存,以便下一次访问时重复使用,节省带宽,提高访问速度,降低服务器压力
expries: (http1.0) 属于过时的验证缓存的方式,其值保存的是服务器返回的过期时间,再次发起请求的时候,如果客户端时间小于资源expires则不会发起请求
cache-control: (http1.1) 现在最多使用的控制缓存的方式,服务器返回的一个相对时间(会设置时间<单位为秒>,在有效时间内发起服务器请求,会读取缓存内容,直至失效,重新发起服务器请求),解决expires比较时间造成的问题,请求服务器资源时,比较缓存资源时间和客户端当前时间(发生更改),则会重新请求资源
通常来说,强缓存不会向浏览器发起请求,直接从缓存中读取内容,在chrome控制台的network的size选项中可以看到 from disk cache 和 from memory cache。并且状态码是200。
- from disk cache:(硬盘中缓存) 一般非脚本内容,例如css/html
- from memory cache: (内存中缓存)一般是脚本、字体、图片
浏览器读取缓存优先级: memory > disk > 服务器请求
浏览器缓存过程
- 浏览器第一次加载资源,服务器返回200,浏览器将资源文件从服务器上请求下载下来,并把response header及该请求的返回时间一并缓存
- 下一次加载资源时,先比较当前时间和上一次返回200时的时间差,如果没有超过cache-control设置的max-age,则没有过期,命中强缓存,不发请求直接从本地缓存读取该文件(如果浏览器不支持HTTP1.1,则用expires判断是否过期);如果时间过期,则向服务器发送header带有If-None-Match和If-Modified-Since的请求
- 服务器收到请求后,优先根据Etag的值判断被请求的文件有没有做修改,Etag值一致则没有修改,命中协商缓存,返回304;如果不一致则有改动,直接返回新的资源文件带上新的Etag值并返回200
- 如果服务器收到的请求没有Etag值,则将If-Modified-Since和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回304;不一致则返回新的last-modified和文件并返回200
强缓存情况下, 设置 cache-control 为 no-cache会走 协商缓存的判断,优先判断ETag , 没有的话判断last-modified对比和服务器资源修改时间,一致的话,返回200, 从缓存中读取
ETag > last-modify
第一次访问资源
第二次访问资源
浏览器输入URL地址后发生了什么
- 浏览器向DNS服务器查找输入URL对应的IP地址
- DNS服务器返回请求域名对应的ip地址
- 浏览器根据IP地址与目标web服务器建立起TCP连接
- 浏览器获取服务器返回的内容
- 浏览器解析html、加载script脚本等
- 窗口关闭后浏览器终止与web服务器连接
参考文章
TCP UDP区别
UDP
- udp没有拥塞控制机制
- udp可以一对一,一对多,多对一,多对多
- udp是无连接的,即发送前不需要建立链接,减少发送数据前的准备时间
- udp是不可靠传输协议; 不保证数据传输的可靠性,因此主机不需要维持连接状态
- udp是面向报文的传输协议;应用层交给udp的报文数据,ud既不拆分也不聚合,一次性发送整个报文数据
TCP
TCP利用检验和,连接管理机制,ACK应答机制,快速重传和超时重传机制,滑动窗口机制,拥塞控制机制,流量控制机制,这些机制共同保证TCP协议的可靠性
- TCP是基于字节流,可以将数据包拆分聚合
- TCP是一对一连接,即每一条TCP连接只能有两个端点
- 面向连接的传输层协议,数据传输开始与断开连接,需要经历3次握手,4次挥手
- TCP是安全,可靠的;保证数据的传输的完整性,有效性。不丢失,无差错、安全有序的到达
什么是流量控制
如果发送方把数据发送得过快,接收方就可能来不及接收,这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快,要让接收方来得及接收
什么是TCP拥塞控制
拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载
为什么TCP需要三次握手
- 防止服务器端开启一些无用的连接增加服务器负载开销
- 防止已失效的连接请求报文段突然又发送到了服务端,因而产生错误
为什么TCP需要四次挥手
服务器收到客户端的 FIN 报文时,内核会马上回一个 ACK 应答报文,但是服务端应用程序可能还有数据要发送,所以并不能马上发送 FIN 报文,而是将发送 FIN 报文的控制权交给服务端应用程序
- 如果服务端应用程序有数据要发送的话,就发完数据后,才调用关闭连接的函数;
- 如果服务端应用程序没有数据要发送的话,可以直接调用关闭连接的函数
是否要发送第三次挥手的控制权不在内核,而是在被动关闭方服务端的应用程序,因为应用程序可能还有数据要发送,由应用程序决定什么时候调用关闭连接的函数,当调用了关闭连接的函数,内核就会发送 FIN 报文了,所以服务端的 ACK 和 FIN 一般都会分开发送
UDP如何实现可靠传输
UDP不属于连接协议,具有资源消耗少,处理速度快的优点传输层无法保证数据的可靠传输,只能通过应用层来实现了。实现的方式可以参照tcp可靠性传输的方式,只是实现不在传输层,实现转移到了应用层
- 添加超时重传机制
- 添加发送和接收缓冲区,主要是用户超时重传
- 添加seq/ack机制,确保数据发送到对端
DNS解析相关 && CDN原理
CDN中文含义”内容分发网络“,将原站的内容分布到接近用户边缘的边缘。用户可以就近获取数据
- 降低了网络请求阻塞情况
- 提高了请求的响应的数据
- 减少了原站的负载压力
DNS解析过程
访问原站的过程(无CDN)
- 用户在客户端输入相关域名。 ”abc.sina.com“;
- 客户端首先会在本地hosts或者hosts缓存中查到域名对应ip地址;
- 如果没有信息,则会到本地DNS查询域名对应服务器ip地址;
- 本地DNS仍然没有域名对应的ip地址,则会依次 根DNS、顶级域名DNS, 权威DNS进行询问。最终本地DNS把域名对应ip地址返回给客户端;
- 客户端拿到对应ip地址,经过标准的TCP三次握手、建立TCP连接; 浏览器向服务器发起数据请求 => 服务器把数据返回给客户端
- 客户端解析数据(html、css、js、image等资源)
- 经过标准的TCP挥手流程,断开TCP连接
域名解析
域名解析可以分为2种
- 将一个域名解析成一个ip地址
- 将一个域名解析成另一个域名
其实解析思路不难,我们在域名服务商购买了一个域名之后,需要去映射一个IP地址,可以用Map来表示这个关系:{域名:IP}。同时我们也可以给某个域名取一个别名,比如“www.baidu.com”取一个别名“test.baidu.com”,这种关系也可以用Map来表示:{域名:别名}。这里的别名专业一点叫做CNAME,相信大家对这个词有点眼熟,它就是这个意思 而域名解析: 最终解析为一个ip地址或者一个CNAME
域名解析是由DNS完成,如果发现域名解析为CNAME,那么继续查询CNAME对应的ip地址
使用CDN获取缓存内容过程
用户使用某个域名来访问静态资源时(这个域名在阿里CDN服务中叫做“加速域名”),比如这个域名为“image.baidu.com”,它对应一个CNAME,叫做“cdn.ali.com”,那么普通DNS服务器(区别CDN专用DNS服务器)在解析“image.baidu.com”时,会先解析成“cdn.ali.com”,普通DNS服务器发现该域名对应的也是一个DNS服务器,那么会将域名解析工作转交给该DNS服务器,该DNS服务器就是CDN专用DNS服务器。CDN专用DNS服务器对“cdn.ali.com”进行解析
- 客户端发起域名请求,先经过本机hosts、hosts缓存查看对应的ip,如果本机没有对应ip地址,转向本地DNS解析、根DNS,顶级DNS依次查找,如果发现该域名对应一个CNAME(CND别名域名)。则会把域名解析权交给
CDN专属DNS服务器(DNS调度系统) - DNS调度系统对域名进行智能解析,将响应速度最快的CDN节点IP地址返回给本地DNS
- 根据请求ip判断用户当前位置,筛选区域内最优的CDN节点
- 本地DNS将ip返给客户端
- 用户向获取的IP地址发起对该资源的访问请求。
- 如果对应节点已经缓存了资源,则会直接把数据返给用户
- 如果该对应节点未缓存该资源。则会向源站请求内容,结合用户自定义配置的缓存策略,把内容缓存到该节点上,并返回给用户,请求结束
参考文章
常见http状态码
- 2xx, 成功–表示请求已被成功接收、理解、接受
- 200 请求成功
- 3xx, 重定向,完成请求必须更进一步的操作
- 301/302区别
- 301(永久移动)
- 请求的内容默认是被缓存
- 请求的网页已永久移动到新位置。后续请求应该向重定向地址发起
- 搜索引擎默认会抓取新的重定向内容,并把网址保存为重定向地址
- 302 (临时移动)
- 请求的资源现在临时从不同的URL响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发 送以后的请求。
- 搜索引擎会抓取重定向网页内容,并把网址保存为原网址
- 只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。
- 这个情况一般就是网站短时间内进行改版,在不影响用户体验的情况下,临时把页面跳转到临时页面
- 4xx:客户端错误–请求有语法错误或请求无法实现。
- 400 (错误请求) 服务器不理解请求的语法
- 401 (未授权) 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应
- 403 服务器拒绝响应
- 404 请求地址找不到
- 5xx:服务器端错误–服务器未能实现合法的请求
- 500 (服务器内部错误) 服务器遇到错误,无法完成请求
- 502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应
- 504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求
请列举常用的 HTTP 方法,并介绍 GET 与 POST 请求之间的区别
- post请求,报文数据量比get多
- get请求数据量较小,可以在URL展示出来,可以被缓存
cookies sessionstorage和localstorage的区别
- cookie:一般由服务端生成,用来标识用户身份
- cookie数据始终在同源的http请求中携带(cookie在浏览器和服务器间来回传递), 每次http请求都会携带cookie 大小为4kB
- sessionstorage和localstorage存储在浏览器中,不参与服务器的通信,大小为5M
- sessionstorage:客户端临时存储数据,当用户关闭浏览器以后,数据就会被删除
- localstorage:客户端存储数据,没有时间限制的数据存储
Cookie操作
- document.cookie = 'xxxx; max-Age: 0'; // 设置cookie过期时间
- http-only,如果cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性
storage提供了一些方法,数据操作比cookie简单
- setItem (key, value) —— 保存数据,以键值对的方式储存信息
- getItem (key) —— 获取数据,将键值传入,即可获取到对应的value值
- removeItem (key) —— 删除单个数据,根据键值移除对应的信息
- clear () —— 删除所有的数据
HTTP 和 HTTPS 的共同点和区别
经典五层模型: 应用层(http/https)、传输层(TCP)、网络层(IP)、数据链路层
http: 超文本传输协议
https: 超文本传输安全协议
- https 协议需要到 ca 申请证书,一般免费证书较少,因而需要一定费用
- http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443
- http 是超文本传输协议,信息是明文传输, https 则是具有安全性的TLS/SSL加密传输协议,相当加了一层(安全层), 更加消费服务器资源
- http请求比https响应更快, 因为https加了TLS,建立连接更加复杂,也要交换更多的数据,难免会影响速度
- http的连接很简单,是无状态的,https协议是有SSL+HTTP 协议构建的可进行加密传输,身份认证的网络协议,比http更加安全
HTTPS加密过程
对称加密
简单来说就是一个密钥,可以用它加密数据,也可以用它解密数据, eg: 客户端和服务器用同一个密钥来进行加密、解密工作 缺点:密钥容易受到第三方劫持
![]()
非对称加密
简单来说就是有2个密钥: 公钥和私钥。若使用私钥加密,公钥可用来解密。反之,可以用公钥加密,私钥解密 服务器维护一组公钥、私钥。服务端TLS阶段传递客户端公钥,客户端用公钥对数据解密。 服务器解密操作。
![]()
握手流程
- 客户端通过URL访问服务器建立TLS连接
- 服务器把证书、公钥、公司信息,证书有效期等信息传递给客户端
- 客户端随机生成一个对称密钥(随机key),在用公钥加密尬该密钥,加密后密钥回传给服务器
- 服务器用私钥解密客户端传递密钥
- ...后续客户端和服务端就用该密钥进行数据通信
每次进行HTTPS请求时都必须在SSL/TLS层进行握手传输密钥吗?
服务器会给每个客户端维护一个sessionId,在TLS阶段传递给浏览器,浏览器生成好的密钥传递给服务器,服务器会把密钥和sessionId对应存在起来,之后再浏览器每次请求的时候 带上sessionId,服务器根据sessionId,找到对应的密钥并进行解密工作。这样就不用每次让客户端只做密钥,服务器保存密钥操作了。
参考文章:
http1.0 / http1.1 / http2.0 区别
http1.0 / http1.1 / http2.0 区别-重点看
http1.1
1、http1.1 增加了connection: keep-alive(保持http持久连接)
假设有6个http请求,如果开启keep-alive, 那么在5-15s同时发送6次http请求,走的是一个TCP连接,走一个是同一条http流,如果超出5-15没有发生http请求,那么连接中断,这表示是一条http长连接,如果不使用http持久连接,那么没发生一次http请求,都会经历TCP三次握手,非常耗时
优点
- 降低服务器因大量建立TCP建立而造成CPU加重负载
缺点
-
以前带宽小,瞬时请求高,所以用这个方法降低 TCP 新建。但现在带宽大,并发高。如果 HTTP 服务存在长轮训或较长间隔请求,而且超过 Keep-Alive 的设置(比如 Keep-Alive 5 秒,但轮训周期是 6 秒),则可能会造成大量的无用途连接,白白占用系统资源。
-
Keep-Alive 可能会非常影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间,额外占用了服务端的连接数,浪费带宽资源。
http 2.0
- 头部压缩: 传递头信息之前会进行压缩
- 二进制分帧:头部信息使用二进制的格式并分成一帧一帧传递 (1.1是一个文本格式),这样虽然对人不友好,但是对计算机非常友好,因为计算机只懂二进制,那么收到报文后,无需再将明文的报文转成二进制,而是直接解析二进制报文
- 多路复用: HTTP2采用二进制格式传输,取代了HTTP1.x的文本格式,二进制格式解析更高效多路复用代替了HTTP1.x的序列和阻塞机制,所有的相同域名请求都通过同一个TCP连接并发完成。
- 在HTTP1.x中,并发多个请求需要多个TCP连接,浏览器为了控制资源会有6-8个TCP连接都限制。
- HTTP2中同域名下所有通信都在单个TCP连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗。
- HTTP2中单个连接上可以并行交错的请求和响应,之间互不干扰
- 服务器推送: 服务器可以对客户端一次请求响应多次,例如客户端请求html,服务器可以额外推送css/js/img资源等
web常用的攻击技术和防护
xss 跨站脚本攻击: XSS 攻击是指攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式
csrf 跨站请求伪造: 通常情况下,CSRF 攻击是攻击者借助受害者的 Cookie 骗取服务器的信任,可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击服务器,从而在并未授权的情况下执行在权限保护之下的操作。
参考文章
什么是跨域
跨域本质是浏览器的同源策略造成,指的是浏览器不能执行其他网站的脚本
什么是同源策略: 协议、域名、端口
- 同源策略限制了一下行为
- Cookie、LocalStorage 和 IndexDB 无法读取
- Ajax请求可以发出来,但是接收不到响应
解决跨域
- 利用jsonp跨域, 传递服务器callback函数,同时callback挂载在window上,
缺点: 只支持get
// 客户端代码
const Jsonp = async (
request: { params: object; callback: string; url: string },
timeout: number = 2000
) => {
return new Promise((resolve, reject) => {
const { url, params, callback } = request;
const method = `${callback}_${Date.now()}`;
let src: string = url.indexOf('?') > -1 ? url : `${url}?`;
for (let key in params) {
src += `&${key}=${params[key]}`;
}
const script = document.createElement('script');
script.setAttribute('src', `${src}&callback=${encodeURIComponent(method)}`);
document.body.appendChild(script);
const clean = () => {
clearTimeout(timer);
delete window[method];
document.body.removeChild(script);
};
const timer = setTimeout(() => {
clean();
reject('timeout 超时啦');
}, timeout);
window[method] = data => {
clean();
resolve(data);
};
script.addEventListener('error', () => {
clean();
reject('资源加载失败');
});
});
};
// 服务端
const qs = require('qs')
const http = require('http')
http
.createServer((req, res) => {
const [path, query] = req.url.split('?')
const params = qs.parse(query, { ignoreQueryPrefix: true })
res.writeHead(200, { 'Content-Type': 'text/javascript' })
setTimeout(() => {
res.end(
`${params.callback}(${JSON.stringify({
path,
name: params.name,
date: params.date,
})})`
)
}, 3000)
})
.listen(8081)
console.log('http serve running at 8081')
- postMessage 跨域
允许来自不同域的脚本使用异步的方式来进行通行,多用于窗口间的数据通信
实现:
[otherWindow].postMessage(message, targetOrigin, [transfer]);
message: 需要传递数据, targetOrigin: 指定origin指定哪些窗口可以接受消息事件(message, 可以设置为 * 标识无限制 )
transfer: 可选,额外的数据对象,一般用不到
// 新crm逻辑, 部分页面跳转到指定老的crm详情页
// searchresult组件 判断是否跳转新老详情页面
private gotoDetail = (item: ITask) => {
let url = `/${ENV_CONFIG.baseName}/task/detail/${item.taskKey}`;
if (getQueryString('viewtype') === 'old' || this.props.isIframe) {
switch (item.taskCategoryCode) {
case ETaskCategory.审批:
case ETaskCategory.升级处理:
case ETaskCategory.拍拍升级处理:
case ETaskCategory.新机退款:
url = `/${ENV_CONFIG.baseName}/task/detail/${item.taskKey}`;
break;
default:
url = `/#/mission/${item.taskKey}`;
}
}
if (this.props.isIframe) {
window.top['postMessage']({ type: 'OPEN_WINDOW', params: url.replace(/\/#\//, '/') }, '*');
return;
}
window.open(url);
};
// 老crm,接受postMessage信息,判断路由跳转, 老crm有嵌套iframe部分
<iframe
v-if="this.conversation.phone || this.orderId"
border="0"
frameborder="no"
marginwidth="0"
marginheight="0"
:src="'/lordaeron/task/searchresult?userMobile=' + this.conversation.phone + '&busiKey=' + this.orderId"
:style="{ width: '100%', height: iframeHeight }"
></iframe>
window.addEventListener("message", (d) => {
let { data } = d;
let { type, params } = data || {};
if (type === "OPEN_WINDOW") {
if (/lordaeron/.test(params)) {
window.open(params);
return;
}
this.routeTo({
path: params,
type: "_blank",
});
}
if (type === "PHONE_CALLING") {
document.querySelector('#dialout_input').value = params;
document.querySelector('#DialEnable').click();
}
});
- 跨域资源共享CORS --- 不支持IE9以下浏览器
- nginx 实现反向代理, webpack devServer proxy 是一个小型服务器,也能实现反向代理指定到指定域名
跨域资源共享CORS(简单请求、复杂请求)
跨站资源共享中的一种方式,它使用额外的http头部告诉浏览器可以服务器进行跨域资源请求
请求类型
简单请求
- 请求方式是 GET, POST, HEAD一种
HEAD请求:和GET请求本质没说什么区别,也是从服务器获取资源,服务器处理逻辑也是一样的。只不过服务器不糊返回请求内容实体。只会回传响应头。
Head请求常用来: 判断资源是否存在 && 检查文件是否最新版本 - content-type值只能是下面几种
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
无定义头部信息
复杂请求
当一个请求不满足上诉简单请求,被判定为复杂请求 复杂请求有2个步骤,开始是options请求(预检请求),然后是实际请求,
CORS预检请求发生在实际请求之前,用于验证服务器是否支持复杂请求,是否安全等、避免跨域请求对用户数据产生未预期结果,在实际业务中经常有自定义头部存在, 这种自定义类型无法预测和保证数据安全,所以需要一个协商的过程(预检请求)
CORS请求过程
CORS请求需要要求服务器设置允许访问来源,即:设置Access-Conrol-Allow-Origin,该字段表明服务器允许哪些站点可以跨域访问资源,
- Origin: 请求发出的原站,如果不在Access-Control-Allow-Origin范围内,则拒绝请求
- Access-Control-Allow-Origin: * ; 代表允许所有站点访问, 注意如果设置 * ,那么头部设置 withCredentials: true, ,无法携带cookie
- Access-control-Allow-Methods: 约定的请求方法(GET/PUT/DELETE/POST/PATCH等)
- Access-Control-Allow-Header: 自定义约束的头部
- Access-Control-Allow-Headers: 跨域请求中,浏览器默认情况下通过API只能获取到以下响应头部字段:
- cache-control
- last-modify
- expries
- content-type
- Access-Control-Allow-Credentials: boolean; 表示在跨域请求是否允许客户端发送cookie给服务器。
浏览器cors请求默认不发生cookie,需要传递cookie的话,客户端和服务端同步设置 withCredentials: true 和 Access-Control-Allow-Credentials: true, 并且Access-Control-Allow-Origin: 不是
*, 不然会报错
使用Promise封装ajax请求
/***
* @description
*
* readyState: 0 请求还未建立,未执行open方法
* readyState: 1 请求已建立,还未调用send方法
* readyState: 2 send方法执行
* readyState: 3 处于响应中,还未得到response
* readyState: 4 响应结束,请求完成处理完成
* */
const levi_ajax = (url, method, params, headers) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url, true); // xhrReq.open(method, url, async); async 是否异步,默认true
for (let key in headers) {
let value = headers(key);
xhr.setRequestHeader(key, value);
}
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject('some errors');
}
}
};
xhr.send(params);
});
};
// 改进,使用onload、onerror监听
const levi_ajaxb = (method, url, params, headers) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
for (let key in headers) {
xhr.setRequestHeader(key, headers[key]);
}
xhr.onload = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(xhr.statusText);
}
}
};
xhr.onerror = () => {
reject('some error');
};
xhr.send(params);
});
};
content-type 有几种 **
用于定义网络文件以及类型,同时告知浏览器以那些方式处理该文件
- 文本: text/html、text/javascript、text/css 、 text/plain
- 图片: image/gif、image/jpeg、image/png
- 视频: video/ogg
- 视频: audio/ogg 、 audio/wav
- 二进制:application/json 、 application/pdf
- 上传文件: multipart/form-data
http报文结构,有哪些headers **
用户http协议交互的信息成为报文: 有请求报文,响应报文, http报文可分为报文首部、报文主体
请求报文
- 请求行(请求方法、请求url、协议版本)
- 请求头部
- 空行
- 请求体: 要传递的参数
实例数据
Request Header
- referer
- cookie
- origin
- accpet
- accept-language
- cache-control
- 客户端自定义请求头参数
响应报文
- 状态行
- 响应头部
- 空格
- 响应体
实例数据
Response Header
- ETag
- date
- expries
- content-type
- last-modified
- ...服务端自定义响应头
参考文章
你一般用的MIME类型有哪些?**
通常来说,浏览器通过MIME Type区分不同的媒体资源, 在把输出结果响应到浏览器上的时候,浏览器必须启动适当的应用程序来处理这个输出文档。这可以通过MIME来完成。在HTTP中,MIME类型被定义在Content-Type header中
mime类型:参考上文:content-type类型