admin 管理员组

文章数量: 887032

文章目录

  • 浏览器
    • 浏览器多线程
    • 浏览器请求网页的过程
    • 浏览器如何解析HTML
      • DOM 树 和 渲染树(render tree) 的区别
      • 浏览器如何解析css选择器
    • 重绘与重排
      • 性能优化:避免重绘与回流
    • 前端安全
      • XSS(Cross Site Script)
        • 什么是XSS
        • 分类
          • 1、反射型XSS
          • 2、存储型XSS
          • 3、DOM XSS
        • 危害
        • 预防
          • HTML编码
          • HTML Attribute编码
          • javaScript编码
          • URL 编码
          • CSS 编码
          • 开启CSP网页安全政策
      • SQL注入
        • 什么是SQL注入
        • 防范
      • [CSRF(Cross-site request forgery)](https://blog.csdn/stpeace/article/details/53512283)
        • CSRF攻击原理及过程
        • CSRF防范
          • (1)验证 HTTP Referer 字段,检查网页来源
          • origin属性
          • (2)在请求地址中添加 token 并验证
          • (3)在 HTTP 头中自定义属性并验证
    • Cookie/Token/session/区别/交互
      • Cookie
        • 作用:区分客户端/保存登陆凭证
        • 存储位置
        • 什么时候携带
        • 同域/跨域ajax请求到底会不会带上cookie?
        • Cookie 的同源和同站
      • Token
        • 作用:访问资源接口(API)时所需要的资源凭证
        • 特点:
        • token 的身份验证流程:
      • [JWT(Json Web Token)](http://www.ruanyifeng/blog/2018/07/json_web_token-tutorial.html)
      • refresh token
      • Session
        • 存储在服务器/记录会话状态/区分用户
        • session 认证流程:
      • Cookie 和 Session 的区别
      • Token 和 Session 的区别
      • Token 和 JWT 的区别
      • token对于session/cookie有什么优点和缺点
      • [Cookie、LocalStorage 与 SessionStorage的区别在哪里?](https://wwwblogs/TigerZhang-home/p/8665348.html)
      • 浏览器缓存
      • 登陆的业务逻辑设计
      • 防止JS获取cookie:http-only、secure-only、host-only
    • URL输入以后到渲染发生了什么
    • get/post请求属于TCP还是UDP
    • get/post的区别是什么
    • 跨域
      • 什么是跨域
      • 同源策略
      • 同站/同源
      • 响应头字段
      • 解决方案
        • CORS
        • JSONP
        • Nginx反向代理
        • 其它跨域⽅案
    • 跨域的登录态是怎么保持的
    • 扫码登录如何实现
    • 前端发送请求的方法总结
    • 验证码实现
    • 浏览器大量http请求怎么优化
    • CSS/JS文件加载是否阻塞DOM解析/渲染
    • xhr与fetch
      • XMLHttpRequest
      • Fetch 优点
      • Fetch的缺点
    • 原生JS操作DOM方法
      • DOM 创建
      • DOM 查询
      • DOM 更改
      • 属性操作
    • 前端缓存
      • HTTP缓存机制
      • 强缓存
        • 为什么出现Cache-Control
      • 协商缓存
        • `Cache-Control` 与 `Expires` 的优先级:
        • Etag生成
      • 离线缓存
        • 概念和优势
        • 离线缓存的优缺点
        • 如何使用
      • HTML5存储类型
        • web storage
        • 离线缓存(application cache)
        • Web SQL
        • IndexedDB
        • Service Worker
      • memory cache 和 disk cache
    • 常⻅的浏览器内核有哪些
    • 前端如何实现即时通讯
    • AJAX原理
    • window.onload DOMContentLoaded区别
    • 单点登陆原理
        • 考虑浏览器

浏览器

浏览器多线程


浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:

  • GUI 渲染线程
  • JavaScript引擎线程
  • 事件触发线程
  • 定时触发器线程
  • 异步http请求线程
  1. GUI渲染线程
    GUI渲染线程负责渲染浏览器界面HTML元素,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
    当界面需要重绘(Repaint)或由于某种操作引发回流(重排)(reflow)时,该线程就会执行。
    在Javascript引擎运行脚本期间,GUI渲染线程都是处于挂起状态的,也就是说被”冻结”了,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行

  2. JavaScript引擎线程
    JavaScript引擎,也可以称为JS内核,主要负责处理Javascript脚本程序,例如V8引擎。
    JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序(单线程)
    注意⚠️:GUI渲染线程和JavaScript引擎线程互斥
    原因:由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JavaScript线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。
    因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JavaScript引擎为互斥的关系,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到引擎线程空闲时立即被执行。
    如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

  3. 事件触发线程
    当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。
    这些事件可以是当前执行的代码块,如定时任务;也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。

  4. 定时触发器线程
    setInterval与setTimeout所在线程
    浏览器定时计数器并不是由JavaScript引擎计数的, 因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确
    通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
    注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。

  5. 异步http请求线程
    在XMLHttpRequest在连接后是通过浏览器新开一个线程请求。
    当检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中,再由JavaScript引擎执行。

浏览器请求网页的过程

从输入页面地址到展示页面信息都发生了些什么?
浏览器加载网页时的过程是什么?- 豆皮范儿的回答 - 知乎
https://www.zhihu/question/30218438/answer/1644739385

  1. DNS解析,查找域名服务器的IP地址,先查看缓存,如果没有再进行递归查询
  • DNS缓存: 浏览器缓存,系统缓存,路由器缓存,ISP服务器缓存,根域名服务器缓存,顶级域名服务器缓存,主域名服务器缓存。通过缓存直接读取域名相对应的ip,减去了繁琐的查找ip的步骤,大大加快访问速度。
  1. 获得IP地址后,进行tcp连接,三次握手

  2. HTTP发起请求

  3. 服务器接收请求对应后台接收到请求,服务器返回浏览器所请求的资源

    服务端接收到请求时,内部会有很多处理:

    • 负载均衡(nginx)
    • 后台处理
  4. 浏览器获取资源后,进行解析并渲染页面

  5. 关闭TCP连接(四次挥手)

浏览器如何解析HTML


从上面这个图上,我们可以看到,浏览器渲染过程如下:

  1. 解析HTML,生成DOM树(并行请求 css/image/js),解析CSS,生成CSSOM树
  2. 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
  3. Layout/reflow(回流):根据生成的渲染树,进行回流(Layout/reflow),得到节点的几何信息(位置,大小)
  4. Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
  5. Display:将像素发送给GPU,展示在页面上


图中重要的四个步骤就是:

1)计算CSS样式 ;
(2)构建渲染树 ;
(3)布局,主要定位坐标和大小,是否换行,各种position overflow z-index属性 ;
(4)绘制,将图像绘制出来。

1)Layout,也称为Reflow,即回流。一般意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树。
2)Repaint,即重绘。意味着元素发生的改变只是影响了元素的一些外观之类的时候(例如,背景色,边框颜色,文字颜色等),此时只需要应用新样式绘制这个元素就可以了。
回流的成本开销要高于重绘,而且一个节点的回流往往回导致子节点以及同级节点的回流, 所以优化方案中一般都包括,尽量避免回流。

DOM 树 和 渲染树(render tree) 的区别

  • DOM 树与 HTML 标签一一对应,包括 head 和隐藏元素
  • 渲染树不包括 head 和隐藏元素,大段文本的每一个行都是独立节点,每一个节点都有对应的 css 属性

  • 在CSS文档中,也同样拥有节点,它的节点与DOM树中的节点是对应的。

    为了构建渲染树,浏览器主要完成了以下工作:
  1. 从DOM树的根节点开始遍历每个可见节点。
  2. 对于每个可见的节点,找到CSSOM树中对应的规则,并应用它们
  3. 根据每个可见节点以及其对应的样式,组合生成渲染树。

浏览器如何解析css选择器

浏览器会**『从右往左』**解析CSS选择器。

从右往左匹配性能更好,是因为从右向左的匹配在第⼀步就筛选掉了⼤量的不符合条件的最右节点(叶⼦节点);⽽从左向右的匹配规则的性能都浪费在了失败的查找上⾯。

重绘与重排

  1. 当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流。每个页面至少需要一次回流,就是在页面第一次加载的时候。

  2. 当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。

注:回流必将引起重绘,而重绘不一定会引起回流。

我们前面知道了,回流这一阶段主要是计算节点的位置和几何信息,那么当页面布局和几何信息发生变化的时候,就需要回流。比如以下情况:

  • 添加或删除可见的DOM元素
  • 元素的位置发生变化
  • 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
  • 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。
  • 页面一开始渲染的时候(这肯定避免不了)
  • 浏览器的窗口尺寸变化(因为回流是根据窗口的大小来计算元素的位置和大小的)
  • 注意:JS 获取 Layout 属性值(如:offsetLeft、offsetWidth、offsetHeightscrollTop、getComputedStyle 等)也会引起回流。因为浏览器需要通过回流计算最新值
  • 激活:hover伪类可能导致重排

性能优化:避免重绘与回流

1.由于display为none的元素在页面不需要渲染,渲染树构建不会包括这些节点;但visibility为hidden的元素会在渲染树中。因为display为none会脱离文档流,visibility为hidden虽然看不到,但类似与透明度为0,其实还在文档流中,还是有渲染的过程。

2.尽量避免使用表格布局,当我们不为表格td添加固定宽度时,一列的td的宽度会以最宽td的宽作为渲染标准,假设前几行td在渲染时都渲染好了,结果下面某行的一个td特别宽,table为了统一宽,前几行的td会回流重新计算宽度,这是个很耗时的事情。

3. 避免使用 css 表达式(expression),因为每次调用都会重新计算值(包括加载页面)
4. 尽量使用 css 属性简写,如:用 border 代替 border-width, border-style, border-color
5. 集中改变样式,往往通过改变class的⽅式来集中改变样式
6. 使⽤DocumentFragment,可以通过createDocumentFragment创建⼀个游离于DOM树之外的节点,然后在此节点上批量操作,最后插⼊ DOM树中,因此只触发⼀次重排
7. 提升为合成层
将元素提升为合成层有以下优点:

  • 合成层的位图,会交由 GPU 合成,⽐ CPU 处理要快
  • 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层
  • 对于 transform 和 opacity 效果,不会触发 layout 和 paint

前端安全

web大前端开发中一些常见的安全性问题

XSS(Cross Site Script)

什么是XSS

跨站脚本攻击。它指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意用户的特殊目的。
它与SQL注入攻击类似,SQL注入攻击中以SQL语句作为用户输入,从而达到查询/修改/删除数据的目的,而在xss攻击中,通过插入恶意脚本,实现对用户游览器的控制,获取用户的一些信息。
该网页把用户通过GET发送过来的表单数据,未经处理直接写入返回的html流,这就是XSS漏洞所在。

分类

XSS有三类:反射型XSS(非持久型)、存储型XSS(持久型)和DOM XSS。

1、反射型XSS

发出请求时,XSS代码出现在URL中,作为输入提交到服务器端,服务器端解析后响应,XSS代码随响应内容一起传回给浏览器,最后浏览器解析执行XSS代码。这个过程像一次反射,故叫反射型XSS。

2、存储型XSS

存储型XSS和反射型XSS的差别仅在于,提交的代码会存储在服务器端(数据库,内存,文件系统等),下次请求目标页面时不用再提交XSS代码。其他用户访问页面时服务器将带有XSS代码的页面返回,用户受到攻击

3、DOM XSS

DOM XSS和反射型XSS、存储型XSS的差别在于DOM XSS的代码并不需要服务器参与,触发XSS靠的是浏览器端的DOM解析,完全是客户端的事情。
触发方式为:http://www.a/xss/domxss.html#alert(1)

这个URL#后的内容是不会发送到服务器端的,仅仅在客户端被接收并解执行。直接输出html内容/直接修改DOM树/替换document URL/打开或修改新窗口

危害
  • 挂马
  • 盗取用户Cookie。
  • DOS(拒绝服务)客户端浏览器。
  • 钓鱼攻击,高级的钓鱼技巧。
  • 删除目标文章、恶意篡改数据、嫁祸。
  • 劫持用户Web行为,甚至进一步渗透内网。
  • 蠕虫式的DDoS攻击。
  • 蠕虫式挂马攻击、刷广告、刷浏量、破坏网上数据
预防

(1)对输入(用户输入/URL参数/POST请求参数等)进行非法字符过滤(对诸如<script>、<img>、<a>等标签进行过滤),对于一些可以预期的输入可以通过限制长度强制截断来进行防御。;
(2)对HTML输出进行编码/转义,这样做浏览器是不会对该标签进行解释执行的,同时也不影响显示效果。
(3)在HTTP 头部配上set-cookie
httponly-这个属性可以防止XSS,它会禁止javascript 脚本来访问cookie。
secure - 这个属性告诉浏览器仅在请求为https 的时候发送cookie。

HTML编码

将不可信数据放入到html标签内(比如div、span等)的时候需要进行html编码。

HTML Attribute编码

需要将不可信数据放入html属性时(不含src、href、style 和 事件处理函数(onclick, onmouseover等))。需要进行HTML Attribute 编码。

javaScript编码

在上面的 XSS 防御HTML Attribute编码中我们是可以防御XSS攻击,但是它只能防御的是HTML通用属性,并不是全部属性,在html中还存在很多支持协议解析的html属性,比如 onclick, onerror, href, src 等这些,类似这些属性我们是无法通过HTML编码来防范XSS攻击的。因为浏览器会先解析html编码的字符,将其转换为该属性的值,但是该属性本身支持JS代码执行,因此游览器在HTML解码后,对该属性的值进行JS解析,因此会执行响应的代码。
JavaScript编码将字符编码成\x+16进制的形式,对款字节编码成Unicode

URL 编码

作用范围:将不可信数据作为 URL 参数值时需要对参数进行 URL 编码

CSS 编码

作用范围:将不可信数据作为 CSS 时进行 CSS 编码
比如:通过css构造(background-img:url\expression\link-href@import)

开启CSP网页安全政策

Content-Security-Policy 中文的意思是 网页安全政策,
要用来防止XSS攻击。是一种由开发者定义的安全性政策申明,通过CSP所约束的责任指定可信的内容来源,通过 Content-Security-Policy 网页的开发者可以控制整个页面中 外部资源 的加载和执行。
比如可以控制哪些 域名下的静态资源可以被页面加载,哪些不能被加载。这样就可以很大程度的防范了 来自 跨站(域名不同) 的脚本攻击。
严格的 CSP 在 XSS 的防范中可以起到以下的作⽤:

  • 禁⽌加载外域代码,防⽌复杂的攻击逻辑
  • 禁⽌外域提交,⽹站被攻击后,⽤户的数据不会泄露到外域
  • 禁⽌内联脚本执⾏(规则较严格,⽬前发现 GitHub 使⽤)
  • 禁⽌未授权的脚本执⾏(新特性,Google Map 移动版在使⽤)
  • 合理使⽤上报可以及时发现 XSS,利于尽快修复问题

SQL注入

什么是SQL注入

是通过客户端的输入把SQL命令注入到一个应用的数据库中,从而执行恶意的SQL语句。
sql被攻击的原因是:sql语句伪造参数,然后对参数进行拼接后形成xss攻击的sql语句。最后会导致数据库被攻击了。
我们来打个比方:我们有一个登录框,需要输入用户名和密码,然后我们的密码输入 'or ‘123’ = '123 这样的。
我们在查询用户名和密码是否正确的时候,本来执行的sql语句是:select * from user where username = ‘’ and password = ‘’. 这样的sql语句,现在我们输入密码是如上这样的,然后我们会通过参数进行拼接,拼接后的sql语句就是:
select * from user where username = ‘’ and password = ’ ’ or ‘123’ = ‘123 ‘; 这样的了,那么会有一个or语句,只要这两个有一个是正确的话,就条件成立,因此 123 = 123 是成立的。因此验证就会被跳过。这只是一个简单的列子,比如还有密码比如是这样的:’; drop table user;, 这样的话,那么sql命令就变成了:
select * from user where username = ‘’ and password = ‘’; drop table user;’ , 那么这个时候我们会把user表直接删除了。

防范

防范的方法:预编译 + 参数化查询

  1. 我们可以使用预编译语句(Prepared Statement),这样的话即使我们使用sql语句伪造成参数,到了服务端的时候,这个伪造sql语句的参数也只是简单的字符,并不能起到攻击的作用
  2. 参数化查询:在使用参数化查询的情况下,数据库服务器不会将参数的内容视为SQL指令的一部份来处理,而是在数据库完成 SQL 指令的编译后,才套用参数运行,因此就算参数中含有恶意的指令,由于已经编译完成,就不会被数据库所运行
  3. 数据库中密码不应明文存储的,可以对密码使用md5进行加密,为了加大破解成本,所以可以采用加盐的方式。

Prepared Statement会对SQL进行预编译,在第一次执行SQL前数据库会进行分析、编译和优化,同时执行计划同样会被缓存起来,它允许数据库做参数化查询。在使用参数化查询的情况下,数据库不会将参数的内容视为SQL执行的一部分,而是作为一个字段的属性值来处理这样就算参数中包含破环性语句(or ‘1=1’)也不会被执行。

CSRF(Cross-site request forgery)

攻击者盗用了你的身份,以你的名义发送恶意请求。 CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账…造成的问题包括:个人隐私泄露以及财产安全。

CSRF攻击原理及过程

CSRF攻击攻击原理及过程如下

  1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
  2. 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
  3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
  4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
  5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。
CSRF防范
  1. 尽量使用post
  2. 加入验证码
    关键操作页面加上验证码,后台收到请求后通过判断验证码可以防御CSRF。但这种方法对用户不太友好。
(1)验证 HTTP Referer 字段,检查网页来源

根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。 这种方法的显而易见的好处就是简单易行,网站的普通开发人员不需要操心 CSRF 的漏洞,只需要在最后给所有安全敏感的请求统一增加一个拦截器来检查 Referer 的值就可以。特别是对于当前现有的系统,不需要改变当前系统的任何已有代码和逻辑,没有风险,非常便捷。

origin属性

**使⽤Origin Header确定来源域名:**通过XMLHttpRequest、Fetch发起的跨站请求或者Post方法发送请求时,都会带上origin,所以服务器可以优先判断Origin属性,再根据实际情况判断是否使用referer判断。

(2)在请求地址中添加 token 并验证

服务端第一次收到请求时(例如登录),会生成一个随机token,并且将Token放置到session当中,返回给客户端,然后在渲染请求页面时把随机数埋入页面(一般埋入 form 表单内)

<input type="hidden" name="_csrf_token" value="xxxx">

客户端可以在 HTTP 请求中以参数的形式提交这个token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。每一次用户界面发过来的请求都会生成一个token值。页面刷新或更改都会导致它的变化。

(3)在 HTTP 头中自定义属性并验证

这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去。

Cookie/Token/session(session有哪些存储方式)/区别/交互

Cookie

作用:区分客户端/保存登陆凭证

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

存储位置
  • 内存cookie,是指没有设在cookie的Expires的属性,此时cookie将停留在客户端的内存中。

  • 硬盘cookie,是指在你设置了cookie的Expires属性,此时cookie将保存到你的硬盘上。

cookie是当你浏览某个网站的时候,由web服务器存储在你的机器硬盘上的一个小的文本文件。 它其中记录了你的用户名、密码、浏览的网页、停留的时间等等信息。当你再次来到这个网站时,web服务器会先看看有没有它上次留下来的cookie。如果有的话,会读取cookie中的内容,来判断使用者,并送出相应的网页内容,比如在页面显示欢迎你的标语,或者让你不用输入ID、密码就直接登录等等。

当客户端要发送http请求时,浏览器会先检查下是否有对应的cookie。有的话,则自动地添加在request header中的cookie字段。注意,每一次的http请求时,如果有cookie,浏览器都会自动带上cookie发送给服务端。那么把什么数据放到cookie中就很重要了,因为很多数据并不是每次请求都需要发给服务端,毕竟会增加网络开销,浪费带宽。所以对于“每次请求都要携带的信息(最典型的就是身份认证信息)”就特别适合放在cookie中,其他类型的数据就不适合了。

简单的说就是:
(1) cookie是以小的文本文件形式(即纯文本),完全存在于客户端;cookie保存了登录的凭证,有了它,只需要在下次请求时带着cookie发送,就不必再重新输入用户名、密码等重新登录了。
(2) 是设计用来在服务端和客户端进行信息传递的;

什么时候携带

服务端可以设置 httpOnly: true, 带有该属性的cookie客户端无法读取

  1. 客户端只会带上与请求同域的cookie, 例如 client/index.html 会带上 client 的cookie, server/app.js 会带上 server 的cookie, 并且也会带上httpOnly的cookie

但是, 如果是向服务端的ajax请求, 则不会带上cookie, 详情见第三个问题

  1. Set-Cookie的Secure属性为false,只有https的情况下才会携带
同域/跨域ajax请求到底会不会带上cookie?

这个问题与你发起ajax请求的方式有关

fetch在默认情况下, 不管是同域还是跨域ajax请求都不会带上cookie, 只有当设置了 credentials 时才会带上该ajax请求所在域的cookie, 服务端需要设置响应头 Access-Control-Allow-Credentials: true, 否则浏览器会因为安全限制而报错, 拿不到响应

axios和jQuery在同域ajax请求时会带上cookie, 跨域请求不会, 跨域请求需要设置 withCredentials 和服务端响应头

Cookie 的同源和同站
  • 同源
    相同 host,不同端口的服务之间,cookie 是可以共享的。
    Cookie 中同源的定义,等价于不考虑 scheme 的同站(schemelessly same site)定义。
    服务端可以通过响应的 Set-Cookie Header,对 Cookie 的可用性进行设定,相关的属性包括 Domain、Path、Secure 等。

  • 同站
    Cookie 的 SameSite 属性

Token

作用:访问资源接口(API)时所需要的资源凭证

简单 token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)

特点:
  • 服务端无状态化、可扩展性好
  • 支持移动端设备
  • 安全
  • 支持跨程序调用
token 的身份验证流程:
  1. 客户端使用用户名跟密码请求登录
  2. 服务端收到请求,去验证用户名与密码
  3. 验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端
  4. 客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里
  5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 token
  6. 服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据
  • 每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里
  • 基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库
  • token 完全由应用管理,所以它可以避开同源策略

JWT(Json Web Token)

  • JSON Web Token(简称 JWT)是目前最流行的跨域认证解决方案。
  • 是一种认证授权机制。
  • JWT 是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准(RFC 7519)。JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。
  • 可以使用 HMAC 算法或者是 RSA 的公/私秘钥对 JWT 进行签名。因为数字签名的存在,这些传递的信息是可信的。

流程:

  1. 用户登陆时,服务端基于用户的数据(用户名、id等),以及一个私钥,对用户数据进行签名(加密),生成一个JWT,然后返回给客服端;
  2. 客户端收到JWT后,存在cookie或localstorage里;
  3. 之后客户端每次向服务端请求/发送资源时,都会带上这个JWT,
    放在 HTTP 请求的头信息Authorization字段里面。
Authorization: Bearer <token>

另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。
4. 服务端对客户端传过来的JWT进行验证(解密),判断用户的请求是否合法/是否有权限进行对应操作,如果合法就进行后续操作,否则返回401没有权限。

refresh token

一个专门生成 access token 的 token,我们称为 refresh token。

access token 用来访问业务接口,由于有效期足够短,盗用风险小,也可以使请求方式更宽松灵活
refresh token 用来获取 access token,有效期可以长一些,通过独立服务和严格的请求方式增加安全性;由于不常验证,也可以如前面的 session 一样处理

有了 refresh token 后,几种情况的请求流程变成这样:


如果 refresh token 也过期了,就只能重新登录了。

Session

存储在服务器/记录会话状态/区分用户
  • session 是另一种记录服务器和客户端会话状态的机制,用来区分用户。
  • session 是基于 cookie 实现的,session 存储在服务器端,sessionId 会被存储到客户端的cookie 中

Session 的存储方式
显然,服务端只是给 cookie 一个 sessionId,而 session 的具体内容(可能包含用户信息、session 状态等),要自己存一下。存储的方式有几种:

  • Redis(推荐):内存型数据库,redis中文官方网站。以 key-value 的形式存,正合 sessionId-sessionData 的场景;且访问快。
  • 内存:直接放到变量里。一旦服务重启就没了
  • 数据库:普通数据库。性能不高。
session 认证流程:
  • 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session
  • 请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器
  • 浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
  • 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
  • 根据以上流程可知,SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态。

Cookie 和 Session 的区别

  • 安全性: Session 比 Cookie 安全,Session 是存储在服务器端的,Cookie 是存储在客户端的。
  • 存取值的类型不同:Cookie 只支持存字符串数据,想要设置其他类型的数据,需要将其转换成字符串,Session 可以存任意数据类型。
  • 有效期不同: Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。
  • 存储大小不同: 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。

Token 和 Session 的区别

还分不清 Cookie、Session、Token、JWT?

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

Token 和 JWT 的区别

相同:

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

区别:

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

token对于session/cookie有什么优点和缺点

  • session弊端:

1、服务器压力增大

通常session是存储在内存中的,每个用户通过认证之后都会将session数据保存在服务器的内存中,而当用户量增大时,服务器的压力增大。

2、CSRF跨站伪造请求攻击

session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

3、扩展性不强

如果将来搭建了多个服务器,虽然每个服务器都执行的是同样的业务逻辑,但是session数据是保存在内存中的(不是共享的),用户第一次访问的是服务器1,当用户再次请求时可能访问的是另外一台服务器2,服务器2获取不到session信息,就判定用户没有登陆过。

4、受同源策略限制

  • Token优点:
  1. 基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库
  2. token 可以避免 CSRF 攻击(因为不需要 cookie 了)
  3. 移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token
  4. token 完全由应用管理,所以它可以避开同源策略
  5. 可扩展:如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token 。

Cookie、LocalStorage 与 SessionStorage的区别在哪里?

  1. cookie始终在同源的http请求中携带,即使不需要,cookie在浏览器和服务器中来回传递。而localStorage和sessionStora仅仅在本地存储,不会和服务器通信,也不会自动把数据发送给服务器。
  2. 存储大小不同,cookie为4kb左右;localStorage, sessionStorage可以达到5M。
  3. localStorage, sessionStorage有现成的API, cookie需要程序员手动封装。
  4. 数据有效期不同,sessionStorage仅在同源窗口中有效,关闭窗口就消失了,cookie可以设置过期时间,localStorage长期有效。

    应用场景:
  • cookie: 比较常用的一个应用场景就是判断用户是否登录。
  • localStorage: 接替了 Cookie 管理购物车的工作;常用于长期登录(+判断用户是否已登录),适合长期保存在本地的数据。
  • sessionStorage: 敏感账号一次性登录;

浏览器缓存

简单来说,浏览器缓存其实就是浏览器保存通过HTTP获取的所有资源,是浏览器将网络资源存储在本地的一种行为。

储存位置:

  1. memory cache
    MemoryCache顾名思义,就是将资源缓存到内存中,等待下次访问时不需要重新下载资源,而直接从内存中获取。Webkit早已支持memoryCache。
    目前Webkit资源分成两类,一类是主资源,比如HTML页面,或者下载项,一类是派生资源,比如HTML页面中内嵌的图片或者脚本链接,分别对应代码中两个类:MainResourceLoader和SubresourceLoader。虽然Webkit支持memoryCache,但是也只是针对派生资源,它对应的类为CachedResource,用于保存原始数据(比如CSS,JS等),以及解码过的图片数据。
  2. disk cache
    DiskCache顾名思义,就是将资源缓存到磁盘中,等待下次访问时不需要重新下载资源,而直接从磁盘中获取,它的直接操作对象为CurlCacheManager。

登陆的业务逻辑设计

防止JS获取cookie:http-only、secure-only、host-only

在服务器端设置cookie的时候设置 http-only, 这样就可以防止用户通过JS获取cookie。对cookie的读写或发送一般有如下字段进行设置:

  • http-only: 只允许http或https请求读取cookie、JS代码是无法读取cookie的(document.cookie会显示http-only的cookie项被自动过滤掉)。发送请求时自动发送cookie.
  • secure-only: 只允许https请求读取,发送请求时自动发送cookie。
  • host-only: 只允许主机域名与domain设置完成一致的网站才能访问该cookie。

URL输入以后到渲染发生了什么

在浏览器中输入URL后,执行的全部过程。(一次完整的http请求过程)
输入网址到网页显示的过程是什么?

  1. 查看浏览器缓存
    强缓存,协商缓存

  2. DNS域名解析

DNS是应用层协议,事实上他是为其他应用层协议工作的,包括不限于HTTP和SMTP以及FTP,用于将用户提供的主机名解析为ip地址。所有DNS请求和回答报文使用的UDP数据报经过端口53发送

浏览器缓存 -> 本地hosts文件 -> 本地DNS解析器缓存 -> 本地DNS服务器 -> 根DNS服务器 -> 顶级域名服务器 -> 权威 DNS 服务器

从客户端到本地DNS服务器是属于递归查询,而DNS服务器之间就是的交互查询就是迭代查询。

DNS解析的过程是什么,求详细的? - wuxinliulei的回答 - 知乎
https://www.zhihu/question/23042131/answer/66571369

为什么域名根服务器只能有13台呢? - 命运之轮的回答 - 知乎
https://www.zhihu/question/22587247/answer/1021961852

  1. 浏览器获得域名对应的 IP 地址后,发起TCP三次握手请求,建立连接

  2. 建立 TCP连接后,浏览器就可以向服务器发送 HTTP 请求(GET/POST/PUT/DELETE…)

  3. 浏览器拿到资源之后对页面进行加载、解析、渲染,最后呈现给用户
    浏览器如何解析HTML

  4. TCP四次握手断开连接

get/post请求属于TCP还是UDP

get请求和post请求的详细区别
GET和POST是什么?HTTP协议中的两种发送请求的方法。

HTTP是什么?HTTP是基于TCP/IP的关于数据如何在万维网中如何通信的协议。

HTTP的底层是TCP/IP。所以GET和POST的底层也是TCP/IP,也就是说,GET/POST都基于TCP连接。GET和POST能做的事情是一样一样的。你要给GET加上request body,给POST带上url参数,技术上是完全行的通的。

get/post的区别是什么

get请求和post请求的详细区别

  1. http中,GET用于向服务器获取数据,而且是安全的和幂等的。* 注意:这里安全的含义仅仅是指是非修改信息

  2. http中,POST是用于向服务器发送数据的请求。

    GET产生一个TCP数据包;POST产生两个TCP数据包;

对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);

而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

跨域

什么是跨域

  • 广义:指一个域下的文档或脚本试图去请求另一个域下的资源;从一个域名的网页去请求另一个域名的资源。比如从www.baidu 页面去请求 www.google 的资源。
  • 狭义:由浏览器同源策略限制的一类请求场景;跨域的严格一点的定义是:只要 协议,域名,端口有任何一个的不同,就被当作是跨域

同源策略

由于浏览器同源策略的限制,非同源下的请求,都会产生跨域问题,jsonp即是为了解决这个问题出现的一种简便解决方案。

**同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。**所以xyz下的js脚本采用ajax读取abc里面的文件数据是会被拒绝的。

  • 同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介
  • 同源策略限制了从同⼀个源加载的⽂档或脚本如何与来⾃另⼀个源的资源进⾏交互。这是⼀个⽤于隔离潜在恶意⽂件的重要安全机制。

如果两个 URL 的 protocolport (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为**“协议/主机/端口元组”**

同源策略即:同一协议,同一域名,同一端口号。当其中一个不满足时,我们的请求即会发生跨域问题。

如果非同源,以下三种行为都将收到限制。

  1. Cookie、LocalStorage 和 IndexDB 无法读取。
  2. DOM 无法获得。
  3. AJAX 请求在浏览器端有跨域限制

不受同源策略限制的

  1. 页面中的链接,重定向以及表单提交是不会受到同源策略限制的。
  2. 跨域资源的引入是可以的。但是js不能读写加载的内容。如嵌入到页面中的<script src="..."></script><img><link><iframe>等。

1、<script src="..."></script> 标签嵌入的跨域脚本。
2、<link rel="stylesheet" href="..."> 标签嵌入的CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的 HTTP 头部 Content-Type 。不同浏览器有不同的限制。
3、<img> 标签嵌入的图片。
4、<video><audio> 标签嵌入的多媒体资源。
5、<object><embed><applet> 标签嵌入的插件。
6、@font-face 引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。
7、<iframe> 载入的任何资源。站点可以使用 X-Frame-Options 消息头来阻止这种形式的跨域交互

同站/同源

站(site)= eTLD+1

TLD 表示顶级域名,例如 等等

TLD+1 表示顶级域名和它前面二级域名的组合,例如网址是:

https://www.example:443/foo

那么:

  • TLD
  • TLD+1example

但是,这种表示方式是有缺陷的,例如对于下面的网址:

https://www.example

如果按照上面的规则,它的 TLD+1 就是 com,并不能表示这个站点,真正能表示这个站点的应该是 example 才对,所以衍生出 eTLD 的概念,即有效顶级域名:

eTLDcom
eTLD+1example

eTLD 由 Mozilla 维护在公共后缀列表(Public Suffix List)中,而「站」的定义就是这里的 eTLD+1

响应头字段

  1. Access-Control-Allow-Credentials
    这里的Credentials(凭证)其意包括:Cookie ,授权标头或 TLS 客户端证书,默认CORS请求是不带Cookies的,这与JSONP不同,JSONP每次请求都携带Cookies的,当然跨域允许带Cookies会导致CSRF漏洞。如果非要跨域传递Cookies,web端需要给ajax设置withCredentials为true,同时,服务器也必须使用Access-Control-Allow-Credentials头响应。此响应头true意味着服务器允许cookies(或其他用户凭据)包含在跨域请求中。另外,简单的GET请求是不预检的,即使请求的时候设置widthCrenditials为true,如果响应头不带Access-Control-Allow-Credentials,则会导致整个响应资源被浏览器忽略
  2. Access-Control-Allow-Headers
  3. Access-Control-Allow-Methods
  4. Access-Control-Allow-Origin
  5. Access-Control-Expose-Headers
    在CORS中,默认的,只允许客户端读取下面六个响应头(在axios响应对象的headers里能看到):
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma

如果这六个以外的响应头要是想让客户端读取到,就需要设置Access-Control-Expose-Headers这个为响应头名了,比如Access-Control-Expose-Headers: Token
6. Access-Control-Max-Age:设置预检请求的有效时长,就是服务器允许的请求方法和请求头做个缓存。

解决方案

CORS

CORS:全称"跨域资源共享"(Cross-origin resource sharing)。

CORS分为简单请求和 **非简单请求(需预检请求)**两类。

对于简单请求,浏览器会直接发送CORS请求,具体说来就是在header中加入origin请求头字段。同样,在响应头中,返回服务器设置的相关CORS头部字段,Access-Control-Allow-Origin字段为允许跨域请求的源。请求时浏览器在请求头的Origin中说明请求的源,服务器收到后发现允许该源跨域请求,则会成功返回。

add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Content-Type';

CORS请求默认不发送Cookie和HTTP认证信息。但是如果要把Cookie发到服务器,要服务器同意,指定Access-Control-Allow-Credentials字段。

add_header 'Access-Control-Allow-Credentials' 'true';

需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。

当发生符合**非简单请求(预检请求)**的条件时,浏览器会自动先发送一个options请求,如果发现服务器支持该请求,则会将真正的请求发送到后端,反之,如果浏览器发现服务端并不支持该请求,则会在控制台抛出错误。

如果非简单请求(预检请求)发送成功,则会在头部多返回以下字段:

Access-Control-Allow-Origin: http://localhost:3001  //该字段表明可供那个源跨域
Access-Control-Allow-Methods: GET, POST, PUT        // 该字段表明服务端支持的请求方法
Access-Control-Allow-Headers: X-Custom-Header       // 实际请求将携带的自定义请求首部字段

优点:CORS支持所有类型的HTTP请求,功能完善;可以通过onerror事件监听错误,并且浏览器控制台会看到报错信息,利于排查。
缺点:目前主流浏览器支持CORS,但IE10以下不支持CORS;

详见:
https://blog.csdn/badmoonc/article/details/82706246

JSONP

为什么jsonp动态创建script标签可以解决跨域:
利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。

优点:简单,兼容性好,可用于解决主流浏览器的跨域数据访问的问题。
缺点:仅支持get方法,具有局限性不安全,可能会遭受XSS攻击;错误处理机制并不完善

JSONP的实现流程

  1. 在客户端声明一个回调函数,其函数名(如show)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)。

  2. 创建一个 <script>标签,把那个跨域的API数据接口地址,赋值给script的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=show)。

  3. 服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是show,它准备好的数据是 show(‘helloworld’)。

  4. 最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(show),对返回的数据进行操作。

详见:
https://blog.csdn/badmoonc/article/details/82289252

Nginx反向代理

反向代理:是指以代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络上的服务器;并将从服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。

例如:

  • 前端server域名为:http://xx_domain
  • 后端server域名为:https://github

现在http://xx_domain对https://github发起请求一定会出现跨域。

不过只需要启动一个nginx服务器,将server_name设置为xx_domain,然后设置相应的location以拦截前端需要跨域的请求,最后将请求代理回github。如下面的配置:

server {
        listen       80;
        server_name  xx_domain;
        location / {
                proxy_pass github.com;
        }
}

其它跨域⽅案
  1. HTML5 XMLHttpRequest 有⼀个API,postMessage()⽅法允许来⾃不同源的脚本采⽤异步⽅式进⾏有限的通信, 可以实现跨⽂本档、多窗⼝、跨域消息传递。
var popup = window.open('http://bbb', 'title');
popup.postMessage('Hello World!', 'http://bbb');

postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。

父窗口和子窗口都可以通过message事件,监听对方的消息。

window.addEventListener('message', function(e) {
  console.log(e.data);
},false);

message事件的事件对象event,提供以下三个属性。

  • event.source:发送消息的窗口
  • event.origin: 消息发向的网址
  • event.data: 消息内容
  1. WebSocket 是⼀种双向通信协议,在建⽴连接之后,WebSocket 的 server 与 client 都能主动向对⽅发送或接收数 据,连接建⽴好了之后 client 与 server 之间的双向通信就与 HTTP ⽆关了,因此可以跨域。
    window.name + iframe:window.name属性值在不同的⻚⾯(甚⾄不同域名)加载后依旧存在,并且可以⽀持⾮常 ⻓的 name 值,我们可以利⽤这个特点进⾏跨域。
  2. location.hash + iframe:a.html欲与c.html跨域相互通信,通过中间⻚b.html来实现。 三个⻚⾯,不同域之间利⽤ iframe的location.hash传值,相同域之间直接js访问来通信。
  3. document.domain + iframe: 该⽅式只能⽤于⼆级域名相同的情况下,⽐如 a.test 和 b.test 适⽤于该⽅ 式,我们只需要给⻚⾯添加
  4. document.domain =‘test’ 表示⼆级域名都相同就可以实现跨域,两个⻚⾯都通过js 强制设置document.domain为基础主域,就实现了同域。

跨域的登录态是怎么保持的

目前,状态判断的一种很流行方式是cookie与session结合实现的。当你输入帐号密码后,向服务器发送请求,服务器判断你登录成功后,会将你的登录状态记录到session中,并且给浏览器返回一个session_id,浏览器在cookie中保存这个session_id,在下一次向服务器请求数据的时候,会通过cookie将session_id传给服务器,服务器通过session_id找到对应的session就可以判断你登录的状态了。

  1. 前端要将withCredentials设为true
  2. 后端要将Access-Control-Allow-Credentials 设为true,因此Access-Control-Allow-Origin就不能设为*了,需要改成具体的域了,这样就可以多次请求取到的sessionid就一致了。

https://blog.csdn/merit_pig/article/details/106446774

扫码登录如何实现

扫码登陆要求已经用户在手机端登录

  1. 用户在浏览器点击扫码登录,浏览器向服务端发送一个请求,服务端生成一个唯一 id,并将这个 id 写入二维码并且返回这个二维码给浏览器获(获取唯一的id, 以及包含id信息的二维码)
  2. 浏览器收到之后,在页面上显示这个二维码,开始用这个 id 轮询后台提供的一个接口,判断用户是否扫码并确认登陆了(浏览器轮询服务器,获取扫码状态),然后根据服务器返回的扫码状态,进行相应的操作
  • 408 扫码超时:如果手机没有扫码或没有授权登录,服务器会阻塞约25s,然后返回状态码 408 -> 前端继续轮询
  • 400 二维码失效:大约5分钟的时间内不扫码,二维码失效
  • 201 已扫码:如果手机已经扫码,服务器立即返回状态码和用户的基本信息
  • 200 已授权:如果手机点击了确认登录,服务器返回200及token -> 前端停止轮询, 获取到token,重定向到目标页
  1. 用户用手机扫描二维码,得到这个 id,再将手机的身份信息与二维码id一起发送给服务端
  2. 服务端将身份信息与二维码id进行绑定,生成一个临时token(只能使用一次)返回给手机端
  3. 手机接收到临时token后弹出确认登陆界面,并确认登陆,然后携带临时token调用服务端接口,告诉服务端已经确认登陆了
  4. 服务器拿到用户信息和 id 之后,写入数据库,并生成token返回给浏览器
  5. 这时候浏览器的服务器轮询就会得到结果,说明用户已经确认登陆,并且得到服务器返回的 token 和用户信息。

前端发送请求的方法总结

  1. 原生的ajax
  2. jquery 发送ajax请求
  3. axios
  4. Vue-resource: this.$http
  5. fetch
  6. 表单

验证码实现

1、后端随机生成一段字符串/数字;

2、将随机字符串存到缓存(或session),同时将字符串生成一张图片/或第三方工具使用短信接口发送给用户;

3、然后将图片的路径放回到前端。前端用img标签展示图片;

4、前端输入的验证码传到后端;

5、后端比对就OK了。

浏览器大量http请求怎么优化

Stalled(阻塞):
  浏览器对同一个主机域名的并发连接数有限制,因此如果当前的连接数已经超过上限,那么其余请求就会被阻塞,等待新的可用连接;此外脚本也会阻塞其他组件的下载;
优化措施:
  1、将资源合理分布到多台主机上,可以提高并发数,但是增加并行下载数量也会增大开销,这取决于带宽和CPU速度,过多的并行下载会降低性能;
  2、脚本置于页面底部;

DNS Lookup(域名解析):
请求某域名下的资源,浏览器需要先通过DNS解析器得到该域名服务器的IP地址。在DNS查找完成之前,浏览器不能从主机名那里下载到任何东西。
优化措施:
  1、利用DNS缓存(设置TTL时间);
  2、利用Connection:keep-alive特性建立持久连接,可以在当前连接上进行多个请求,无需再进行域名解析;

Request sent(发送请求):
  发送HTTP请求的时间(从第一个bit到最后一个bit)
优化措施:
  1、减少HTTP请求,可以使用CSS Sprites、内联图片、合并脚本和样式表等;
  2、**浏览器缓存:**对不常变化的组件添加长久的Expires头(相当于设置久远的过期时间),在后续的页面浏览中可以避免不必要的HTTP请求;

Waiting(等待响应):
通常是耗费时间最长的。从发送请求到收到响应之间的空隙,会受到线路、服务器距离等因素的影响。
优化措施:
  使用CDN(内容分发网络),将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求,提高响应速度;

Content Download(下载):
  下载HTTP响应的时间(包含头部和响应体)
优化措施:
  1、浏览器缓存:通过条件Get请求,对比If-Modified-Since和Last-Modified时间,确定是否使用缓存中的组件,服务器会返回“304 Not Modified”状态码,减小响应的大小;
  2、移除重复脚本,精简和压缩代码,如借助自动化构建工具webpack、grunt、gulp等
  3、压缩响应内容,服务器端启用gzip压缩,可以减少下载时间

CSS/JS文件加载是否阻塞DOM解析/渲染

  1. CSS:不会阻塞DOM解析,会阻塞DOM树的渲染,会阻塞后面js语句的执行
  • 加载css的时候,可能会修改下面DOM节点的样式,如果css加载不阻塞DOM树渲染的话,那么当css加载完之后,DOM树可能又得重新重绘或者回流了,这就造成了一些没有必要的损耗。所以干脆就先把DOM树的结构先解析完,把可以做的工作做完,然后等你css加载完之后,在根据最终的样式来渲染DOM树,这种做法性能方面确实会比较好一点。
  • 为了避免让用户看到长时间的白屏时间,我们应该尽可能的提高css加载速度,比如可以使用以下几种方法:
  1. 使用CDN(因为CDN会根据你的网络状况,替你挑选最近的一个具有缓存内容的节点为你提供资源,因此可以减少加载时间)
  2. 对css进行压缩(可以用很多打包工具,比如webpack,gulp等,也可以通过开启gzip压缩)
  3. 合理的使用缓存(设置cache-control,expires,以及E-tag都是不错的,不过要注意一个问题,就是文件更新后,你要避免缓存而带来的影响。其中一个解决防范是在文件名字后面加一个版本号)
  4. 减少http请求数,将多个css文件合并,或者是干脆直接写成内联样式(内联样式的一个缺点就是不能缓存)
  1. JS:会阻塞DOM解析,没有async/defer属性的script标签会阻塞DOM渲染
  • 浏览器并不知道脚本的内容是什么,如果先行解析下面的DOM,万一脚本内全删了后面的DOM,浏览器就白干活了。更别谈丧心病狂的document.write。浏览器无法预估里面的内容,那就干脆全部停住,等脚本执行完再干活就好了。
  • 浏览器遇到 <script>且没有defer或async属性的标签时,会触发页面渲染,因而如果前面CSS资源尚未加载完毕时,浏览器会等待它加载完毕在执行脚本。
  1. 总结
    <script> 最好放底部,<link>最好放头部,如果头部同时有<script><link>的情况下,最好将<script>放在<link>上面

xhr与fetch

fetch 同 XMLHttpRequest 非常类似,都是用来做网络请求。但是同复杂的XMLHttpRequest的API相比,fetch使用了Promise,这让它使用起来更加简洁,从而避免陷入”回调地狱”。

XMLHttpRequest

  1. 创建XHRHttpResquest对象
var xmlHttp;
if (typeof XMLHttpRequest != "undefined") {
    // 高版本浏览器都支持
    xmlHttp = new XMLHttpRequest();
} else if (window.ActiveXObject) {
    // 其他低版本浏览器
    var aVersions = ["Msxml2.XMLHttp.5.0", "Msxml2.XMLHttp.4.0", 
        "Msxml2.XMLHttp.3.0", "Msxml2.XMLHttp", "Microsoft.XMLHttp"];
    for (var i = 0; i < aVersions.length; i++) {
        try {
            xmlHttp = new ActiveXObject(aVersions[i]);
            break;
        } catch (e) {}
    }
}

大体流程

 xhr.timeout = xxx;
 // 初始化一个请求
 xhr.open(method, url, async);
 // setRequestHeader 必须在 open() 之后、send() 之前调用
 xhr.setRequestHeader(header, value)
 //设置响应返回的数据格式
 xhr.responseType = "text";
 // 监听状态
 xhr.onreadystatechange = function() {
     if (xhr.status === 2) {
         // 返回所有的响应头
         console.log(xhr.getAllResponseHeaders())
         // 返回特定响应头 
         // ⚠️注意:规定客户端无法获取 response 中的 Set-Cookie、Set-Cookie2这2个字段,无论是同域还是跨域请求。
         //规定对于跨域请求,客户端允许获取的response header字段只限于 simple response header
         xhr.getResponseHeader("Content-Type");
     }
     if (xhr.status === 4 || xhr.readyState ===200){
         // 返回的纯文本的值
         console.log(xhr.responseText)
         // HTML 节点或解析后的 XML 节点,也可能是在没有收到任何数据或数据类型错误的情况下返回的 null
         console.log(xhr.responseXML);
     }
 }
 // 超时处理
 xhr.ontimeout = function() { xxx }
 // 错误处理
 xhr.onerror = function() {xxx}
 // 发送请求
 xhr.send(data) // POST数据放在data中,GET会忽略这个参数

 // xhr.send(data)中data参数的数据类型会影响请求头部content-type的默认值:
 //如果data是 Document 类型,同时也是HTML Document类型,则content-type默认值为text/html;charset=UTF-8;否则为application/xml;charset=UTF-8;
 //如果data是 DOMString 类型,content-type默认值为text/plain;charset=UTF-8;
 //如果data是 FormData 类型,content-type默认值为multipart/form-data; boundary=[xxx]
 //如果data是其他类型,则不会设置content-type的默认值
 //当然这些只是content-type的默认值,但如果用xhr.setRequestHeader()手动设置了中content-type的值,以上默认值就会被覆盖。

获取上传和下载进度

xhr.onprogress = progessFn;
xhr.upload.onprogress = progessFn

function progessFn(event) {
    if (event.lengthComputable) {
        console.log(event.loaded / event.total)
    }
}

Fetch 优点

MDN - fetch()

  1. fetch的语法简洁,更语义化
  2. 基于promise,支持async/await
  3. 同构方便,使用isomorphic-fetch

Fetch的缺点

  1. fetch只对网络错误报错,http状态码错误不报错
  2. fetch不支持abort,无法终止
  3. fetch不支持超时控制,使用setTimeout和Promise.reject实现的超时控制不能阻止请求过程继续在后台运行,造成了流量的浪费
  4. fetch没有原生检测请求进度的方式,XHR可以
  5. 默认情况下fetch不发送cookie,除非手动配置

原生JS操作DOM方法

DOM 创建

var el1 = document.createElement('div');
var node = document.createTextNode('hello world!');

DOM 查询

// 返回当前文档中第一个类名为 "myclass" 的元素
var el = document.querySelector(".myclass");

// 返回一个文档中所有的class为"note"或者 "alert"的div元素
var els = document.querySelectorAll("div.note, div.alert");

// 获取元素
var el = document.getElementById('xxx');
var els = document.getElementsByClassName('highlight');
var els = document.getElementsByTagName('td');

// 获取父元素、父节点
var parent = ele.parentElement;
var parent = ele.parentNode;

// 获取子节点,子节点可以是任何一种节点,可以通过nodeType来判断
var nodes = ele.children;    

// 查询子元素
var els = ele.getElementsByTagName('td');
var els = ele.getElementsByClassName('highlight');

// 当前元素的第一个/最后一个子元素节点
var el = ele.firstChild;
var el = ele.firstElementChild;

var el = ele.lastChild;
var el = ele.lastElementChild;

// 下一个/上一个兄弟元素节点
var el = ele.nextSibling;
var el = ele.nextElementSibling;

var el = ele.previousSibling;
var el = ele.previousElementSibling;

DOM 更改

// 添加、删除子元素
ele.appendChild(el);
ele.removeChild(el);

// 替换子元素
ele.replaceChild(el1, el2);

// 插入子元素
parentElement.insertBefore(newElement, referenceElement);

属性操作

// 获取一个{name, value}的数组
var attrs = el.attributes;

// 获取、设置属性
var c = el.getAttribute('class');
el.setAttribute('class', 'highlight');

// 判断、移除属性
el.hasAttribute('class');
el.removeAttribute('class');

// 是否有属性设置
el.hasAttributes();     

前端缓存

HTTP缓存机制



强缓存

在浏览器第一次发出请求之后,需要再次发送请求的时候,浏览器首先获取该资源缓存的 header 信息,然后根据 Cache-ControlExpires 字段判断缓存是否过期。如果没有过期,直接使用浏览器缓存,并不会与服务器通信。该过程为判断是否使用强缓存,即本地缓存。

  1. Cache-Control 字段是 HTTP1.1 规范,一般利用该字段的 max-age 属性来判断,这个值是一个相对时间,单位为 s,代表资源的有效期。例如:
Cache-Control:max-age=3600

除此之外还有几个常用的值:

  • no-cache:表示不使用强缓存,需要使用协商缓存
  • no-store:禁止浏览器缓存数据,每次请求下载完整的资源
  • public:可以被所有用户缓存,包括终端用户和中间代理服务器
  • private:只能被终端用户的浏览器缓存
  1. Expires字段是 HTTP1.0 规范,他是一个绝对时间的 GMT 格式的时间字符串。例如:
expires:Mar, 06 Apr 2020 10:57:09 GMT

这个时间代表资源的失效时间只要发送请求的时间在这之前,都会使用强缓存

为什么出现Cache-Control

由于失效时间是一个绝对时间(本地时间),因此当服务器时间客户端时间偏差较大时,就会导致缓存混乱

值得注意的是expires时间可能存在客户端时间跟服务端时间不一致的问题。所以,建议expires结合Cache-Control一起使用

而Cache-Control的max-age是一个相对时间,消除了服务器时间和客户端时间偏差带来的问题。

协商缓存

如果缓存过期,浏览器会向服务器发送请求,即使用协商缓存。本次请求会带着第一次请求返回的有关缓存的 header 字段信息,比如以下两个字段:

  1. Etag/If-None-Match
    ETag是响应头,If-None-Match是请求头。
    Last-Modified / If-Modified-Since的主要缺点就是它只能精确到秒的级别,一旦在一秒的时间里出现了多次修改,那么Last-Modified / If-Modified-Since是无法体现的。

判断响应头中是否存在 Etag 字段,如果存在,浏览器则发送一个带有 If-None-Match 字段的请求头的请求,该字段的值为 Etag 值。服务器通过对比客户端发过来的Etag值是否与服务器相同。如果相同,说明缓存命中,服务器返回 304 状态码,并将 If-None-Match 设为 false, 客户端继续使用本地缓存。如果不相同,说明缓存未命中,服务器返回 200 状态码,并将 If-None-Match 设为 true,并且返回请求的数据

  1. Last-Modified/If-Modified-Since
    Last-Modified是响应头,If-Modified-Since是请求头

除了 Etag 字段之外,客户端还会通过服务器返回的 Last-Modified 字段判断是否继续使用缓存,该字段为服务器返回的资源的最后修改时间,为UMT时间。浏览器发送一个带有 If-Modified-Since 字段的请求头的请求给服务器,该字段的值为 Last-Modified 值。服务器收到之后,通过这个时间判断,在该时间之后,资源有无修改,如果未修改,缓存命中,返回 304 状态码;如果未命中,返回 200 状态码,并返回最新的内容

Cache-ControlExpires 的优先级:

​两者可以在服务端配置同时使用,Cache-Control 的优先级高于 Expires

Last-Modified/If-Modified-Since 已经可以判断缓存是否失效了,为什么出现 Etag/If-None-Match?

​ Etag/If-None-Match 是实体标签,是一个资源的唯一标识符,资源的变化都会导致 ETag 的变化。出现 Etag 的主要原因是解决 Last-Modified 比较难解决的问题:

  • 一些文件也许会周期性的修改,但是他的内容并不发生改变,这个时候我们并不希望客户端认为这个文件修改了
  • 某些文件在秒以下的时间内进行修改了,If-Modified-Since无法判断。UNIX时间只能精确到秒

Last-Modified 和 Etag 可以一起使用, Etag 的优先级更高。

刷新页面的问题:

F5刷新:不使用强缓存,使用协商缓存

ctrl+F5:二者都不使用

Etag生成

总结:nginx 中 etag 由响应头的 Last-Modified 与 Content-Length 表示为十六进制组合而成。

离线缓存

概念和优势


离线缓存是Html5新特性之一,简单理解就是第一次加载后将数据缓存,在没有清除缓存前提下,下一次没有网络也可以加载,用在静态数据的网页或游戏比较好用。
(1)在没有网络的时候可以访问到缓存的对应的站点页面,包括html,js,css,img等等文件

(2)在有网络的时候,浏览器会优先使用已离线存储的文件,返回一个200(from cache)头。这跟HTTP的缓存实用策略是不同的

(3)资源的缓存可以带来更好的用户体验,当用户使用自己的流量上网时,本地缓存不仅可以提高用户访问速度,而且大大节约用户的使用流量。

离线缓存的优缺点

优点

  1. 减少服务器的负载,提高资源加载速度
  2. 离线浏览,用户可以在应用离线时使用

缺点

  1. 更新版本后,必须刷新一次才会启动新版本。
  2. 进入离线存储的页面,如果不更新版本,是会将其当成静态页面不请求。
  3. 无法进行灰度发布等策略

​离线缓存与传统浏览器缓存区别:

  1. 离线缓存是针对整个应用,浏览器缓存是单个文件
  2. 离线缓存断网了还是可以打开页面,浏览器缓存不行
  3. 离线缓存可以主动通知浏览器更新资源
如何使用

(1)在需要缓存的和html文件的根节点(html)添加manifest属性,属性值是当前目录下的一个.appcache文件/或.manifest


2)编写test.mainfest文件。

CACHE MANIFEST//必须以这个开头
version 1.0 //最好定义版本,更新的时候只需修改版本号
CACHE:
a.css
NETWORK:
b.css
FALLBACK:
c.ss   a.css

说明:CACHE下面的都是缓存的文件,NETWORK表示每次都从网络请求,FALLBACK:指定的文件若是找不到,会被重定向到新的地址。注意,第一行必须是”CACHE MANIFEST”文字,以把本文件的作用告知浏览器,即对本地缓存中的资源文件进行具体设置。


说明:浏览器从APPcache中获取资源及数据,APPcache会访问服务器查看是否有资源更新,没有则直接返回,有的话先更新下载服务端资源并缓存本地,之后更新浏览器显示最新数据。

HTML5存储类型

web storage
  1. localStorage:适用于长期存储数据,以键值对(Key-Value)的方式存储,永久存储,永不失效,除非手动删除。浏览器关闭后数据不丢失;每个域名5M
  2. sessionStorage:存储的数据在浏览器关闭后自动删除。⽽且与cookie、localStorage不同,他不能在所有同源窗⼝中共享,是会话级别的储存⽅式
离线缓存(application cache)

见上方离线缓存

Web SQL

关系数据库,通过SQL语句访问。
Web SQL 数据库 API 并不是 HTML5 规范的一部分,但是它是一个独立的规范,引入了一组使用 SQL 操作客户端数据库的 APIs。web sql类似于SQLite,是真正意义上的关系型数据库,⽤sql进⾏操作,当我们⽤JavaScript时要进⾏转换,较为繁琐。

核心方法:
openDatabase:这个方法使用现有的数据库或者新建的数据库创建一个数据库对象。
transaction:这个方法让我们能够控制一个事务,以及基于这种情况执行提交或者回滚。
executeSql:这个方法用于执行实际的 SQL 查询。

IndexedDB

索引数据库 (IndexedDB) API(作为 HTML5 的一部分)对创建具有丰富本地存储数据的数据密集型的离线 HTML5 Web 应用程序很有用。同时它还有助于本地缓存数据,使传统在线 Web 应用程序(比如移动 Web 应用程序)能够更快地运行和响应。

是被正式纳⼊HTML5标准的数据库储存⽅案,它是NoSQL数据库,⽤键值对进⾏储存,可以进⾏快速读取操作,⾮常适合web场景,同时⽤JavaScript进⾏操作会⾮常⽅便。

打开数据库:

window.indexedDB.open('testDB')

关闭与删除:

function closeDB(db){
     db.close();
}
function deleteDB(name){
     indexedDB.deleteDatabase(name);
}

数据存储:

indexedDB中没有表的概念,而是objectStore,**一个数据库中可以包含多个objectStore,objectStore是一个灵活的数据结构,可以存放多种类型数据。**也就是说一个objectStore相当于一张表,里面存储的每条数据和一个键相关联。

我们可以使用每条记录中的某个指定字段作为键值(keyPath),也可以使用自动生成的递增数字作为键值(keyGenerator),也可以不指定。选择键的类型不同,objectStore可以存储的数据结构也有差异。

Service Worker

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。

Service Worker 实现缓存功能一般分为三个步骤:

  1. 首先需要先注册 Service Worker
  2. 然后监听到 install 事件以后就可以缓存需要的文件
  3. 那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。

memory cache 和 disk cache

MemoryCache顾名思义,就是将资源缓存到内存中,等待下次访问时不需要重新下载资源,而直接从内存中获取
目前Webkit资源分成两类,一类是主资源比如HTML页面,或者下载项一类是派生资源,比如HTML页面中内嵌的图片或者脚本链接,分别对应代码中两个类:MainResourceLoader和SubresourceLoader。虽然Webkit支持memoryCache,但是也只是针对派生资源,它对应的类为CachedResource,用于保存原始数据(比如CSS,JS等),以及解码过的图片数据。

diskCache顾名思义,就是将资源缓存到磁盘中,等待下次访问时不需要重新下载资源,而直接从磁盘中获取。它与memoryCache最大的区别在于,当退出进程时,内存中的数据会被清空,而磁盘的数据不会,所以,当下次再进入该进程时,该进程仍可以从diskCache中获得数据,而memoryCache则不行。
diskCache与memoryCache相似之处就是也只能存储一些派生类资源文件。它的存储形式为一个index.dat文件,记录存储数据的url,然后再分别存储该url的response信息和content内容。Response信息最大作用就是用于判断服务器上该url的content内容是否被修改。

小结:一般图片会用disk cache(非脚本文件), js文件用memory cache(脚本文件)

常⻅的浏览器内核有哪些

前端如何实现即时通讯

前端如何实现即时通讯?

AJAX原理

Ajax相当于在用户和服务器之间加了—个中间层,使用户操作与服务器响应异步化。并不是所有的用户请求都提交给服务器,像—些数据验证和数据处理等都交给Ajax引擎自己来做, 只有确定需要从服务器读取新数据时再由Ajax引擎代为向服务器提交请求。

Ajax的原理简单来说就是:通过XmlHttpRequest对象来向服务器发送异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。这其中最关键的一步就是从服务器获得请求数据。要清楚这个过程和原理,我们必须对 XMLHttpRequest有所了解。

XMLHttpRequest是ajax的核心机制,它是在IE5中首先引入的,是一种支持异步请求的技术。简单的说,也就是javascript可以及时向服务器提出请求和处理响应,而不阻塞用户。达到无刷新的效果。

第一步:创建XMLHttpRuquest对象;
第二步:注册回调方法
第三步:设置和服务器交互的相应参数
第四步:设置向服务器端发送的数据,启动和服务器端的交互
第五步:判断和服务器端的交互是否完成,还要判断服务器端是否返回正确的数据

window.onload DOMContentLoaded区别

DOM完整的解析过程:

  1. 解析HTML结构。
  2. 加载外部脚本和样式表文件。
  3. 解析并执行脚本代码。//js之类的
  4. DOM树构建完成。//DOMContentLoaded
  5. 加载图片等外部文件。
  6. 页面加载完毕。//load

在第4步的时候DOMContentLoaded事件会被触发。
在第6步的时候onload事件会被触发。

1、当 onload事件触发时,页面上所有的DOM,样式表,脚本,图片,flash都已经加载完成了。
2、当 DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片,flash。

  • 为什么要区分?
    开发中我们经常需要给一些元素的事件绑定处理函数。但问题是,如果那个元素还没有加载到页面上,但是绑定事件已经执行完了,是没有效果的。这两个事件大致就是用来避免这样一种情况,将绑定的函数放在这两个事件的回调中,保证能在页面的某些元素加载完毕之后再绑定事件的函数。
    当然DOMContentLoaded机制更加合理,因为我们可以容忍图片,flash延迟加载,却不可以容忍看见内容后页面不可交互。

单点登陆原理

在同域下的客户端/服务端认证系统中,通过客户端携带凭证,维持一段时间内的登录状态。

但当我们业务线越来越多,就会有更多业务系统分散到不同域名下,就需要「一次登录,全线通用」的能力,叫做「单点登录」。

  1. 用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
  2. sso认证中心发现用户未登录,将用户引导至登录页面
  3. 用户输入用户名密码提交登录申请
  4. sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌
  5. sso认证中心带着令牌跳转会最初的请求地址(系统1)
  6. 系统1拿到令牌,去sso认证中心校验令牌是否有效
  7. sso认证中心校验令牌,返回有效,注册系统1
  8. 系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源
  9. 用户访问系统2的受保护资源
  10. 系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
  11. sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌
  12. 系统2拿到令牌,去sso认证中心校验令牌是否有效
  13. sso认证中心校验令牌,返回有效,注册系统2
  14. 系统2使用该令牌创建与用户的局部会话,返回受保护资源

用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心,全局会话与局部会话有如下约束关系

  1. 局部会话存在,全局会话一定存在
  2. 全局会话存在,局部会话不一定存在
  3. 全局会话销毁,局部会话必须销毁
考虑浏览器

对浏览器来说,SSO 域下返回的数据要怎么存,才能在访问 A 的时候带上?浏览器对跨域有严格限制,cookie、localStorage 等方式都是有域限制的。

这就需要也只能由 A 提供 A 域下存储凭证的能力。一般我们是这么做的:

  • 在 SSO 域下,SSO 不是通过接口把 ticket 直接返回,而是通过一个带 code 的 URL 重定向到系统 A 的接口上,这个接口通常在 A 向 SSO 注册时约定
  • 浏览器被重定向到 A 域下,带着 code 访问了 A 的 callback 接口,callback 接口通过 code 换取 ticket
  • 这个 code 不同于 ticket,code 是一次性的,暴露在 URL 中,只为了传一下换 ticket,换完就失效
  • callback 接口拿到 ticket 后,在自己的域下 set cookie 成功
  • 在后续请求中,只需要把 cookie 中的 ticket 解析出来,去 SSO 验证就好
    访问 B 系统也是一样

本文标签: 浏览器