1、Cookie与Session
HTTP 是无状态的协议。每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。而这个状态需要通过 cookie 或者 session 去实现。
1)Cookie
cookie 存储在客户端: cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,cookie数据不能超过4K,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。cookie 是不可跨域的,cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。
Expires 设置 cookie 的过期时间(时间戳),这个时间是客户端时间;
Max-Age 设置 cookie 的保留时长(秒数),同时存在 Expires 和 Max-Age 的话,Max-Age 优先;
Domain 设置生效的域名,默认就是当前域名,不包含子域名;
Path 设置生效路径,/ 全匹配;
Secure 设置 cookie 只在 https 下发送,防止中间人攻击;
HttpOnly 设置禁止 JavaScript 访问 cookie,防止XSS;
SameSite 设置跨域时不携带 cookie,防止CSRF;
Secure 和 HttpOnly 是强烈建议开启的。
2)Session
session 是另一种记录服务器和客户端会话状态的机制。session 是基于 cookie 实现的,session 存储在服务器端,sessionId 会被存储到客户端的cookie 中。
session 认证流程:
a、用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session;
b、请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器;
c、浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名;
d、当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
Cookie 和 Session 的区别:
a、安全性:Session 比 Cookie 安全,Session 是存储在服务器端的,Cookie 是存储在客户端的。
b、存取值的类型不同:Cookie 只支持存字符串数据,想要设置其他类型的数据,需要将其转换成字符串,Session 可以存任意数据类型。
c、有效期不同:Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。
d、存储大小不同:单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。
2、Token
Token分为访问令牌 Acesss Token和刷新令牌Refresh Token。
1)Acesss Token
Acesss Token是访问资源接口(API)时所需要的资源凭证。简单 token 的组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)。
特点:服务端无状态化、可扩展性好;支持移动端设备;安全;支持跨程序调用。
token 的身份验证流程:
a、客户端使用用户名跟密码请求登录。
b、务端收到请求,去验证用户名与密码,验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端。
c、客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里。
d、客户端每次向服务端请求资源的时候需要带着服务端签发的 token,需要把 token 放到 HTTP 的 Header 里;服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据。
e、基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据(服务端不保存用户信息),只保存token。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库。
f、token 完全由应用管理,所以它可以避开同源策略。
2)Refresh Token
refresh token 是专用于刷新 access token 的 token。如果没有 refresh token,也可以刷新 access token,但每次刷新都要用户输入登录用户名与密码,会很麻烦。有了 refresh token,可以减少这个麻烦,客户端直接用 refresh token 去更新 access token,无需用户进行额外的操作。
a、Access Token 的有效期比较短,当 Acesss Token 由于过期而失效时,使用 Refresh Token 就可以获取到新的 Token,如果 Refresh Token 也失效了,用户就只能重新登录了。
b、Refresh Token 及过期时间是存储在服务器的数据库中,只有在申请新的 Acesss Token 时才会验证,不会对业务接口响应时间造成影响,也不需要向 Session 一样一直保持在内存中以应对大量的请求。
Token 和 Session 的区别:
a、Session 是一种记录服务器和客户端会话状态的机制,使服务端有状态化,可以记录会话信息。而 Token 是令牌,访问资源接口(API)时所需要的资源凭证。Token 使服务端无状态化,不会存储会话信息。
b、Session 和 Token 并不矛盾,作为身份认证 Token 安全性比 Session 好,因为每一个请求都有签名还能防止监听以及重放攻击,而 Session 就必须依赖链路层来保障通讯安全了。如果你需要实现有状态的会话,仍然可以增加 Session 来在服务器端保存一些状态。
c、所谓 Session 认证只是简单的把 User 信息存储到 Session 里,因为 SessionID 的不可预测性,暂且认为是安全的。而 Token ,如果指的是 OAuth Token 或类似的机制的话,提供的是 认证 和 授权 ,认证是针对用户,授权是针对 App 。其目的是让某 App 有权利访问某用户的信息。这里的 Token 是唯一的。不可以转移到其它 App上,也不可以转到其它用户上。Session 只提供一种简单的认证,即只要有此 SessionID ,即认为有此 User 的全部权利。是需要严格保密的,这个数据应该只保存在站方,不应该共享给其它网站或者第三方 App。所以简单来说:如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token 。
3、Web Storage
Web Storage 的本地储存方案,其包括LocalStorage、SessionStorage。localStorage始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;SessionStorage 和 LocalStorage 使用方法基本一致,唯一不同的是,sessionStorage仅在当前浏览器窗口关闭前有效,一旦关闭页面,SessionStorage 将会删除数据;
LocalStorage 的特点是:使用 Key-Value 形式储存,Key 和 Value 以字符串形式储存;大小有 10MB;
大概工作流程就是,用户登录后,从服务器拿到一个 token,然后存进 LocalStorage 里,之后每次请求前都从 LocalStorage 里取出 token,放到请求数据里,服务器就能知道是同一个用户在发起请求了;
LocalStorage的缺点:
无法像 Cookie 一样设置过期时间;
只能存入字符串,无法直接存对象;要解决这个问题,一般是使用 JSON.stringify() 配合 JSON.parse()。
由于JSON.stringify() 本身是存在一些问题。JSON.stringify()无法正确转换 JS 的部分属性:undefiend、Function、RegExp(正则表达式,转换后变成了空对象)、Date(转换后变成了字符串,而非 Date 类的对象)
localStorage最大容量是5M,如果我们要存的数据超过5M了该怎么办呢?其实,localStorage最大容量5M的意思是每一个域名下的localStorage容量是5M,假如现在a.com域名下localstorage存不下了,我们可以使用iframe创建b.com域框架(子页面)用于存储a.com剩下的数据。然后使用postMessage读写数据。
window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机 (两个页面的模数Document.domain设置为相同的值) 时,这两个脚本才能相互通信。
4、indexedDB
indexedDB是一种低级API,用于客户端存储大量结构化数据(包括, 文件/ blobs)。该API使用索引来实现对该数据的高性能搜索。
IndexedDB 是一个事务型数据库系统,是一个基于 JavaScript 的面向对象数据库。
indexedDB的使用基本使用步骤如下:
a、打开数据库并且开始一个事务。
b、创建一个 object store。
c、构建一个请求来执行一些数据库操作,像增加或提取数据等。
d、通过监听正确类型的 DOM 事件以等待操作完成。
e、在操作结果上进行一些操作(可以在 request对象中找到)。
5、cookie、sessionStorage、localStorage三者的区别
存储大小:cookie数据大小不能超过4k,sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。localStorage的值为string类型
有效时间:localStorage存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage数据在当前浏览器窗口关闭后自动删除;cookie设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。
数据与服务器之间的交互方式:cookie的数据会自动的传递到服务器,服务器端也可以写cookie到客户端;sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。
不同浏览器无法共享localStorage,相同浏览器的不同页面间可以共享相同的 localStorage(页面属于相同域名和端口),但是不同页面或标签页间无法共享sessionStorage的信息。这里需要注意的是,页面及标 签页仅指顶级窗口,如果一个标签页包含多个iframe标签且他们属于同源页面,那么他们之间是可以共享sessionStorage的。
存储过多数据会导致页面变卡,因为localStorage的本质是对字符串的读取。即localStorage的关键问题在于它是通过同步操作的方式来进行文件I/o操作。写入localStorage的数据都会保存到磁盘上,除非主动删除数据,否则数据是永远不会过期的。对于文件的I/O是非常昂贵和不一致的(不可信赖)。任何时间点任何的程序都可以访问文件。在理想状态下,你读取的文件不会有其他程序在同一时间访问该文件。在极端坏的情况下,如果你想读取一个文件,就必须等待文件上的锁被释放(其他程序操作文件时会锁定文件)。
这就引申出一个浏览器的问题:到底什么时候磁盘的数据才应该被读取?只有两种可能。
第一种,数据可以在页面加载时就被读取,这样可以确保后面的读取快速操作。当然,这也意味着localStorage将会影响页面的加载时间,即使localStorage读取的数据并不会使用。在理想情况下,你并不会注意到这有多大不同。但在极端坏的情况下,这可能会导致页面加载时间的延长。
第二种方式是在localStorage第一次被使用(JS操作)的时候再从磁盘读取数据。这样可以阻止对于页面加载的中断,但也意味着在第一次通过localStorage访问数据的时候浏览器会中断对于页面的处理(js执行、页面渲染等)。磁盘文件的所有localStorage数据都会被写进内存以加快后面对于localStorage数据的读取速度。同样地,通过localStorage保存的数据会首先写入内存,以后再写入到磁盘文件里,以加快写的速度。firefox和chrome都使用了第二种方式(opera好像也是如此,我没有验证过)。
6、从页面 A 打开一个新页面 B,B 页面关闭后,如何通知 A 页面?
A 页面打开 B 页面,A、B 页面通信方式
1)url 传参
A 页面通过 url 传递参数与 B 页面通信,同样通过监听hashchange事件,在页面 B 关闭时与 A 通信。
2)postmessage
postMessage 是 h5 引入的 API,postMessage() 方法允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档、多窗口、跨域消息传递,可在多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案。
3)localStorage
localStorage仅允许你访问一个Document源(origin)的对象Storage;存储的数据将保存在浏览器会话中。如果 A 打开的 B 页面和 A 是不同源,则无法访问同一Storage。
4)WebSocket
基于服务端的页面通信方式,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
5)Service Worker
Service Worker 是一个可以长期运行在后台的 Worker,能够实现与页面的双向通信。多页面共享间的 Service Worker 可以共享,将 Service Worker 作为消息的处理中心(中央站)即可实现广播效果。
B 页面正常关闭,如何通知 A 页面
页面正常关闭时,会先执行 window.onbeforeunload ,然后执行window.onunload ,我们可以在这两个方法里向 A 页面通信。
B 页面意外崩溃,又该如何通知 A 页面
页面正常关闭,我们有相关的 API,崩溃就不一样了,页面看不见了,JS 都不运行了,那还有什么办法可以获取B页面的崩溃?
发现我们可以利用 window 对象的 load 和beforeunload 事件,通过心跳监控来获取 B 页面的崩溃。这个方案巧妙的利用了页面崩溃无法触发beforeunload事件来实现的。
在页面加载时(load事件)在sessionStorage记录good_exit状态为pending,如果用户正常退出(beforeunload事件)状态改为true,如果crash了,状态依然为pending,在用户第2次访问网页的时候(第2个load事件),查看good_exit的状态,如果仍然是pending就是可以断定上次访问网页崩溃了!
但有一个问题,本例中用 sessionStorage 保存状态,在用户关闭了B页面,sessionStorage 值就会丢失,所以换种方式,使用 Service Worker 来实现:
a、Service Worker 有自己独立的工作线程,与网页区分开,网页崩溃了,Service Worker 一般情况下不会崩溃;
b、Service Worker 生命周期一般要比网页还要长,可以用来监控网页的状态;
c、网页可以通过navigator.serviceWorker.controller.postMessageAPI 向掌管自己的 SW 发送消息。
所以提出了以下流程:
a、B 页面加载后,通过postMessageAPI 每5s给 sw 发送一个心跳,表示自己的在线,sw 将在线的网页登记下来,更新登记时间;
b、B 页面在beforeunload时,通过postMessageAPI 告知自己已经正常关闭,sw 将登记的网页清除;
c、如果 B页面在运行的过程中 crash 了,sw 中的running状态将不会被清除,更新时间停留在奔溃前的最后一次心跳;
d、A 页面 Service Worker 每10s查看一遍登记中的网页,发现登记时间已经超出了一定时间(比如 15s)即可判定该网页 crash 了。