围绕cookie的职能-用户追踪、身份验证、存储展开,包含token、web存储storage、indexedDB

247 阅读11分钟

cookie

因为其存储、与服务器交流的性质,决定其作用-身份验证、用户追踪、存储

cookie相关的安全问题,推荐查看另一篇博客

用户追踪:cookie与session

因为HTTP协议无状态,所以使用cookie和session来跟踪浏览器用户身份的会话方式,两者位置不同:

无状态就是服务端并不会保存身份认证相关的数据。

  • cookie数据存放在客户的浏览器
  • session数据放在服务器上 ,可以存放会话过程中的任何对象。其创建与使用总是在服务端,浏览器从来有得到过session对象。
  • session更安全 :别人可以分析存放在本地的cookie并进行cookie欺骗
  • 减轻服务器负担 :应当使用cookie
  • 新开的浏览器窗口会生成新的Session,子窗口除外,子窗口会共用父窗口的Session。
  • 使用session机制的时候也需要cookie的帮助保存session_id

如果客户端浏览器将Cookie功能禁用,或者不支持Cookie怎么办?绝大多数的手机浏览器都不支持Cookie。

Java Web提供了另一种解决方案:URL地址重写 在URL参数的前面添加了字符串“jsessionid=XXX”。其中XXX为Session的id。增添的jsessionid字符串不会影响请求的文件名,也不会影响提交的地址栏参数。用户单击这个链接的时候会把Session的id通过URL提交到服务器上,服务器通过解析URL地址获得Session的id。

身份验证:cookie、token

cookie

cookie

  1. C输入登陆凭据
  2. S验证凭据
  3. S创建会话,然后把会话数据存储在数据库中
  4. S返回保存会话id的cookie【session机制】\cookie状态信息【cookie机制】给用户浏览器
  5. 后续请求中,S会根据数据库验证会话id\处理cookie
  6. 一旦用户登出,C和S销毁该会话。
C第一次发起请求:
GET /sample_page.html HTTP/1.1
Host: www.example.org

S返回:
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: 采用key:value形式,根据需要来指定过期时间、域、路径、有效期、适用站点

C再次发起请求:
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: 服务器通过set-cookie给予的信息

在cookie与set-cookie中,包含的常用的cookie信息:

  1. storedId cookie的id
  2. name名称:不区分大小写,唯一标识
  3. Path:控制作用域,指定了主机下的哪些路径可以接受 Cookie(该 URL 路径必须存在于请求 URL 中)。以字符 %x2F ("/") 作为路径分隔符,子路径也会被匹配。
  4. value值,必须经过URL编码
  5. domain路径:指定了哪些主机可以接受 Cookie。如果不指定,默认为 origin,不包含子域名。如果指定了Domain,则一般包含子域名。因此,指定 Domain 比省略它的限制要少。如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如developer.mozilla.org)
  6. session:是否是session cookie
  7. expires过期时间
  8. 安全标志(非必需)
  9. SameSite
  10. HttpOnly :防止通过 JavaScript 访问 cookie 值,JavaScript 可以通过跨站脚本攻击(XSS)的方式来窃取 Cookie。
  11. secure布尔值,默认false,可以在不安全的http上传输,设置为true-必须通过https或者其他安全协议传输

在这里插入图片描述

token

token = uid+time+sign[+固定参数]

  1. C输入登陆凭据
  2. S验证凭据
  3. S返回一个经过签名的token
  4. C负责存储token
  5. 后续的请求需要携带token;
  6. S对JWT进行解码,如果token有效,则处理该请求;
  7. 一旦用户登出,C销毁token。
 Token最好保存在SessionStorage而不是localstorage中
 每次页面加载时,使用JS遍历整个DOM树,向DOM中所有的a和form标签后加入Token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的HTML代码,还需要程序员在编码时手动添加Token。
一般也不会放在 Cookie 里自动发送,更好的做法是放在 HTTP HeaderAuthorization字段中:Authorization: Token

实现token有很多方法, JSON Web Tokens(JWT)是一种较为认可的标准

  • JWT JSON Web Token,为JSON对象, 用于各方之间的信息安全地传输
  • 尺寸小: JWT可以通过URL,POST参数或者HTTP头发送
  • 自包含:token中包含了用户的所有必须信息,避免了多次查询数据库

云信SDK的身份验证实现,就是登录之后返回Token

token的优势

  • 后端服务不需要记录token,而是验证传入的token是否有效,以及在成功的登陆请求上签署token,相比查找数据库,计算更快,提升了性能
  • 多站点使用

cookie绑定到单个域,foo.com域产生的cookie无法被bar.com域读取。不同窗口/tab 同一域操作的 cookies 是同一个。

  • 支持移动平台,在移动平台上cookie是不被支持的。

  • token可以抵抗csrf,cookie+session不行

token是开发者为了防范csrf而特别设计的令牌,浏览器不会自动添加到headers里,攻击者也无法访问用户的token 。

前端缓存

  • 方法都不会加密数据,如果有这方面应该考虑先加密,e.g:MD5加密
  • W3C标准的浏览器:运行在不同域的JavaScript无法读写其他域的cookie和localstorage 在这里插入图片描述 共享一个对象引用,不是复制,不同窗口/tab 同一域操作的 cookies 是同一个

cookie可以通过设置Path来控制作用域,但是不能将其设置为服务器父域名之外的其他域名。

cookie

浏览器为特定网页或者网站保存的少量命名数据,作为http协议的扩展实现,自动在浏览器和服务器之间传输。

JavaScript通过 Document.cookie 属性可创建新的 Cookie,也可通过该属性访问非HttpOnly标记的Cookie

allCookies = document.cookie; // 读取
document.cookie = newCookie; //写 :newCookie是一个键值对形式的字符串。需要注意的是,用这个方法一次只能对一个cookie进行设置或更新。

cookie的值字符串可以用encodeURIComponent()来保证它不包含任何逗号、分号或空格(cookie值中禁止使用这些值)
document.cookie=endodeURIComponent(name)+"="+endodeURIComponent(value);
CookieUtil.get("名称“);

encodeURIComponent存入的cookie值将来要通过decodeURIComponent来解码

cookie默认生命周期是会话,但是可以通过expires、max-age来控制操作,使用前可以定义一个库来包装它的增删查改 developer.mozilla.org/zh-CN/docs/…

  • cookie保存在客户端,容易随着用户的操作导致cookie丢失或者被窃取。因此适合存储对安全性要求不高,但是需要长时间保存的数据

子cookie

发送回服务器 绕过浏览器对每个域cookie数的限制,提出了子cookie的概念,在单个cookie存储的小块数据,本质上是使用cookie的值在单个cookie中存储多个名值对

name=name1=value1&name2=value2&name3=value3

实际开发中注意大小,不要超过对单个cookie的限制 操作子cookie需要添加辅助方法,解析和序列化子cookie串 cookie数据不是保存在安全的环境中,不要存储敏感重要信息

web Storage(同步)

  • webstorage是本地存储,存储在客户端,HTML5的WebStorage提供了两种API:localStorage(本地存储)和sessionStorage(会话存储)。

  • WebStorage 支持事件通知机制,可以将数据更新的通知发送给监听者。storage改变事件

  • 同步阻塞写入数据

  • Web Storage 的 api 接口使用更方便。而cookie的原生接口不友好,需要程序员自己封装

  • localStorage和sessionStorage都具有相同的操作方法,例如setItem、getItem和removeItem等

  • localStorage和sessionStorage的key和length属性实现遍历

  • localStorage和sessionStorage只能存储字符串类型,对于复杂的对象可以使用ECMAScript提供的JSON对象的stringify和parse来处理。被存储的键值对会自动转换成字符串形式,以UTF-16 DOMString 的格式所存储,使用两个字节来表示一个字符。 应用场景:

  1. localStoragese:常用于长期登录(+判断用户是否已登录),适合长期保存在本地的数据
  2. sessionStorage:敏感账号一次性登

sessionStorage

数据保存在session对象,不适用于跨会话持久存储。只存储会话数据(即存储到浏览器关闭),不受页面刷新影响,可以在浏览器崩溃并重启后恢复。

  • 页面中一般的js对象的生存期仅在当前页面有效,刷新页面、转到另一页面等重新加载页面的情况,数据消失
  • 而sessionStorage只要同源的同窗口中,刷新页面或进入同源的不同页面,数据始终存在,也就是说只要浏览器不关闭,会话存在,数据就存在
  • sessionStorage 属性允许你访问一个,对应当前源(包括特定协议)的 session Storage 对象。同源的页面及标签页仅指顶级窗口,如果一个标签页包含多个iframe标签且他们属于同源页面,那么可以共享sessionStorage

会话:传输层的概念,TCP 的三次握手也创建了一次会话,TCP 四次挥手关闭连接则关闭了会话。输入账户密码进入系统——退出系统

  • 页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。
  • 在新标签或窗口打开一个页面时会复制顶级浏览会话的上下文作为新会话的上下文,这点和 session cookies 的运行方式不同。
  • 打开多个相同的URL的Tabs页面,会创建各自的sessionStorage。所以通过浏览器右键新开的tab不能访问原页面的,见图:

关闭对应浏览器窗口(Window)/ tab,会清除对应的sessionStorage。 在这里插入图片描述 后面收到一个博主的回复,得知 浏览器版本更新导致了上述方案失效,更新修稿了a属性的rel默认属性为"noopener",而noopener是不会复制sessionStorage的

// rel 属性用于指定当前文档与被链接文档的关系
<a rel="opener" target='_blank' href='file:///C:/Users/Administrator/Desktop/%E7%AB%96%E6%8E%92.html'>打开</a>

localStorage

  • localStorage 用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去删除。
  • 本地html文件共享localStorage,访问百度页面并不会共享本地的localStorage
  • 在客户端持久存储数据的机制,数据保存在客户端本地的硬件设备,会保留到通过JavaScript删除或者用户清除浏览器缓存。
  • localStorage和cookie在所有同源窗口中都是共享的
var userData = {
        name : 'Lucy',
        age: 18
    }
    localStorage.setItem('userDate', JSON.stringify(userData));
    var newUserData = JSON.parse(localStorage.getItem('userData')); 

应用:web应用可在没有网络时候访问,可以开发离线应用

web storage事件处理程序

利用storage事件实时监控web storage 中的数据

window.addEventListener('storage',function (event) {

} 

在事件处理函数中,触发事件的事件对象(event参数值)具有如下几个属性

  1. event.key 属性:属性值为在 session 或 localStorage 中被修改的数据键值。
  2. event.oldValue 属性:属性值为在 sessionStorage 或 localStorage 中被修改的值。
  3. event.newValue 属性:属性值为在 sessionStorage 或 localStorage 中被修改后的值
  4. event.url 属性:属性值为修改 sessionStorage 或 localStorage 中值的页面的URL地址
  5. event.storageArea 属性 : 属性值为被变动的 sessionStorage 对象或 localStorage 对象

indexedDB数据库(异步)

IndexedDB 可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。

在浏览器的开发者选项中的resources选项卡中可以查看
与页面源绑定,不管用户链接状态如何都可以访问,类似localStorage
每个域名(严格的说,是协议 + 域名 + 端口)都可以新建任意多个数据库。
浏览器中存储结构化数据的一个方案,对象存储的集合,采用结构化克隆算法-意味着可以拥有map、set、定型数组、二进制数据(ArrayBuffer 对象和 Blob

事务型对象存储数据库

  • 按照事务分组,原子性
  • Window.indexedDB.open();//返回请求结果而非数据库连接
  • 版本化,修改则添加新版本。同一个时刻,只能有一个版本的数据库存在。
  • 每个数据库包含若干个对象仓库(object store),类似于关系型数据库的表格。

打开/新建数据库-admin成功后,获取/新建 对象仓库-users

返回一个 IDBRequest 对象。这个对象通过三种事件errorsuccessupgradeneeded,处理打开数据库的操作结果来获取/新建对象仓库-users,类似table

{keyPath:"usernames"}建立主键,objectStore.get(主键)方法用于读取数据

let db,request,version=1;
//存在则打开,不存在就创建
request =indexedDB.open("admin",version0);
request.onerror=(event)=>
	alert(‘Failed to open:${event.target.errorCode}');
request.onsuccess=(event)=>{
//获取数据库对象
	db=event.target.result;
};
//对象记录,存入DB中需要指定一个键keyPath
let user={
	username:"001",
	firstname:"ja",
	password:"fff"
};
// 数据库键users,db.objectStore("users")访问
if(db.objectStoreNames.contains("users")){
	db.deleteObjectStore("users");
}else{
	const store = db.createObjectStore("users",{keyPath:"usernames"});
}

为 对象仓库 新建索引

如果不建立索引,默认只能搜索主键(即从主键取值)

objectStore.createIndex('name', 'name', { unique: false });

使用索引

let transaction = db.transaction(['person'], 'readonly');
let store = transaction.objectStore('person');
let index = store.index('name');
let request = index.get('李四');

request.onsuccess = function (e) {
  let result = e.target.result;
  if (result) {
    // ...
  } else {
    // ...
  }
}

事务

  • 读取和修改数据都是通过事务操作,原子性
  • 事务对象本身也有事件处理程序onerror和oncomplete获取事务的错误、成功状态
let transaction=db.transaction("users",”访问读写模式“);
//不指定对象时,默认所有对象;也可以传入对象数组;不指定访问读写模式,默认只读
transaction.onerror=(event)=>{
	//
}
transaction.oncomplete=(event)=>{
	//
}
  • 事务transaction实现增删查改
// new 一个blob对象
var obj1 = {hello: "world"};
var blob = new Blob([JSON.stringify(obj1, null, 2)], {type : 'application/json'});

function add() {
  var request = db.transaction(['imgLists'],  'readwrite')
    .objectStore('imgLists')
    .add({ id: 1, name: '图片1', path: '/static/image', blob:  blob});

  request.onsuccess = function (event) {
    console.log('数据写入成功');
  };

  request.onerror = function (event) {
    console.log('数据写入失败');
  }
}

创建游标

通过已知键取得一条记录,如果想取得多条数据,在事务中创建游标,一个指向结果集的指针

可以传入两个参数:键范围和方向
null 所有对象
next、prev
nextunique、prevunique
const transaction=db.transaction("users",”访问读写模式");
store=transaction.objectStore("users");
	request=store.openCursor();//
	
	request.onerror=(event)=>{
	//
    };
    request.onsuccess=(event)=>{
	//
	const cursor=event.target.result;
	if(cursor){//检查
		console.log(`Key:${cursor.key},Value:${JSON.stringify(cursor.value)}`);
		cursor.continue();
		}else{
			console.log("done");
	}
    };

键范围

const onlyRange=IDBKeyRange.only("007");
const lowerRange=IDBKeyRange.lowerBound("007");//007后面的
const upperRange=IDBKeyRange.upperBound("007");//007前面的
const boundRange=IDBKeyRange.bound("007","ace");//007-"ace",
const boundRange=IDBKeyRange.bound("007","ace",true,true);//007的下一条-"ace"的前一条,