Token,Cookie,IndexedDB总结

1.cookie 与 localStorage
  • cookie是客户端用来保存用户状态的(处理session会话),是完成交互式应用的必备条件。当然也可以存储kv键值对(例如存储token等),但是cookie的大小限制是4k,只能存储有限的数据。

cookie在第一次发出请求时(或者登录或者请求页面),响应会在Set-Cookie中返回一定的信息,用作记录用户的状态,该状态会保存在客户端浏览器中,一般在对同一个域名发请求时会携带此cookie,服务端验证请求中的cookie并判断如何响应。过程如下


cookie在请求与响应中的作用

cookie中的kv对还有其他的一些属性
①Expires属性:设置Cookie的生存期。在java中可以通过setMaxAge属性设置cookie的生存期

Cookie cookie = new Cookie("name","jianshu");
// 设置生命周期为Integer.MAX_VALUE,永久有效,会持久化到浏览器中
cookie.setMaxAge(Integer.MAX_VALUE);
// 设置maxAge为正数,持久化到浏览器中指定时间,不管是否关闭浏览器
cookie.setMaxAge(Integer.MAX_VALUE);
//当maxAge属性为负数,则表示该Cookie只是一个临时Cookie,不会被持久化,仅在本浏览器窗口或者本窗口打开的子窗口中有效,关闭浏览器后该Cookie立即失效
cookie.setMaxAge(Integer.MAX_VALUE);
//当maxAge为0时,表示立即删除同名的Cookie
cookie.setMaxAge(0);
resp.addCookie(cookie);

②Path属性:定义了访问例如“/test”路径时是否可以携带该cookie
③Domain属性:指定了可以访问该 Cookie 的 Web 站点或域。Cookie 机制并未遵循严格的同源策略,可以设置父域的Cookie。当设置domain为域.jianshu.com,aa.jianshu.com和bb.jianshu.com使用同样的cookie。单点登录时会有用处,然而也增加了 Cookie受攻击的危险,比如攻击者可以借此发动会话定置攻击。
④Secure属性:指定是否使用https安全协议发送Cookie。使用HTTPS安全协议,可以保护Cookie在浏览器和Web服务器间的传输过程中不被窃取和篡改。
⑤HttpOnly 属性 :防止客户端脚本通过document.cookie属性访问Cookie,有助于保护Cookie不被跨站脚本攻击窃取或篡改。但是,HTTPOnly的应用仍存在局限性,一些浏览器可以阻止客户端脚本对Cookie的读操作,但允许写操作;此外大多数浏览器仍允许通过xml对象读取响应中的Set-Cookie

  • localStorage/sessionStorage:cookie由于存储大小有限,浏览器设计了专门用于存储数据量的仓库,大概为几兆(各个浏览器提供的不同),虽然有点不是特别大,但是对于一般的应用基本够用了。它们对于单页面应用的制作有一定的作用。

sessionStorage用于存储会话信息,localStorage用于永久性存储。它们对于大多数浏览器都是可用的,而且使用也非常简单,详细使用可以查看localStorage使用总结

  • WebSql/IndexedDB:localStorage虽然能够完成了小数据量的存储,但是不能实现大数据量和条件查询。为了完成上述两个功能,浏览器又创造了IndexedDB与webSql,这两个对象都可以在本地创建一个前端数据库。目前IndexedDB相比WebSql兼容的浏览器较多,IndexedDB不仅提供查找接口,还能建立索引。

IndexedDB有数据库和数据仓库的概念分别对应关系型数据库中的数据库和表,不同的是数据库有版本的区别,同一个时刻,只能有一个版本的数据库存在。如果要修改数据库结构(新增或删除表、索引或者主键),只能通过升级数据库版本完成
下面是IndexedDB的简单使用

//新建数据库与打开数据库都是这步操作,但是如果是新建数据库会发生版本升级可以从onupgradeneeded事件中得到db对象,而打开数据库会从onsuccess事件中得到db
//注意:打开数据库时如果指定的版本号,大于数据库的实际版本号,也会走onupgradeneeded事件。
var request = window.indexedDB.open(databaseName, version);
//新建数据库时监听的事件
request.onupgradeneeded = function(event) {
 var db = event.target.result;//得到数据库对象
}
//打开数据库时监听的事件
request.onsuccess = function (event) {
   var db = request.result//得到数据库对象
}
//新建表和主键
var objectStore = db.createObjectStore(tableName, { keyPath: 'id' });
//新建索引,参数索引名称、索引所在的属性、配置对象
//主键(key)是默认建立索引的属性。比如,数据记录是{ id: 1, name: '张三' },那么id属性可以作为主键
var index = objectStore.createIndex(indexName, indexColumn, { unique: true });
//数据的增删改查都需要使用事务,所以执行增删改查时先新建一个事务对象,其中的第二个参数是操作模式("只读"或"读写")
var transaction= db.transaction([tableName], 'readwrite')
//通过事务得到数据表对象执行增删改查的操作
var objectStore = transaction.objectStore(tableName);
objectStore .add({ id: 1, name: 'aa', age: 24, email: 'aa@123.com' })//增加数据
var request = objectStore.get(1);//通过主键查询
request.onsuccess = function( event) {
     if (request.result) {
       console.log('Name: ' + request.result);
   }
};
objectStore .put({ id: 1, name: 'bb', age: 35, email: 'bb@123.com'});//修改主键为1的数据
objectStore .delete(1);//删除主键为1的数据

IndexedDB与WebSql不同,它不使用sql语句,属于NoSql的一种,而且有很多优点,例如支持事务,异步等等,详情可以查看浏览器数据库 IndexedDB 入门教程

2.cors跨域请求

1.同源策略

为了保存用户的登陆状态,我们使用浏览器cookie+服务器端session的方式。用户登陆成功之后,服务器会生成对应的session保存会话状态,然后将session保存在Set-Cookie中返回给我们;下次请求携带cookie,服务器就知道是谁在访问了。
但是,如果A网站的cookie被其他网站拿到,那么其他网站就有可能使用我们的cookie在A网站做一些危险操作,比如转账等。这种利用其他网站登录状态的操作叫做跨域请求伪造(csrf)。所以为了防止跨域攻击,浏览器只会发送同源(协议相同,域名相同,端口相同)网站的cookie,例如,访问http://www.baidu.com网站时,只会发送该网站的cookie数据,不会发送http://www.qq.com的cookie。这就叫做同源策略。同源策略包括:
①无法读取其他网站的Cookie、LocalStorage 和 IndexDB 数据。为了让a网站读取b网站的cookie,可以利用上面说的domain属性
②iframe页面嵌套不同源页面时,DOM元素无法获得(window.open打开的跨源窗口也不可以。iframe页面可以使用window.postMessage方法完成通信同源策略与请求
③不能发送不同源的ajax请求。为了支持ajax的跨源操作,需要用到跨域资源共享(cors)。跨域资源共享与普通的ajax请求相同,但是浏览器在了解到是跨域请求后便会自动执行,不需要用户,但是服务端需要根据请求源判断时候同意跨域。跨域资源共享分为简单请求与非简单请求:

  • 如果是get,post,head请求方而且请求参数只包括Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type(只限于application/x-www-form-urlencoded、multipart/form-data、text/plain)时称为简单请求。简单请求是为了兼容表单提交模式。对于简单请求,浏览器会增加一个Origin字段(指明请求的源),这个字段是服务器来判断跨域是否被允许的依据。如果跨域请求被允许,则会返回
//该字段必须存在,表示发起请求的域名,如果允许所有的网站跨域访问则写成*;
***需要注意的是:java中response可能不能设置多个域名,所以先只能循环判断,在设置请求的域名***
Access-Control-Allow-Origin: http://www.otherhome.com
//该字段可选。表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
//除此之外xhr也需要设置成可以发送cookie,使用***xhr.withCredentials = true***。需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名
Access-Control-Allow-Credentials: true
//该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('myheader')可以返回myheader字段的值。
Access-Control-Expose-Headers: myheader
Content-Type: text/html; charset=utf-8
  • 如果非get,post,head请求则统称为非简单请求,非简单请求需要在请求之前发起一次预检请求,预检请求使用OPTIONS方法。整个过程也只不需要用户参与。预检请求形如:
//预检请求的请求数据
OPTIONS /cors HTTP/1.1
//预检请求需要传递Origin
Origin: http://www.otherhome.com
//非简单请求的方法
Access-Control-Request-Method: PUT
//非简单请求的需要传递的请求头参数
Access-Control-Request-Headers: Custom-Header
Host: api.alice.com
User-Agent: Mozilla/5.0


//预检请求的响应数据
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
//请求端允许请求的域,与简单请求的使用方法相同。如果不允许该网站跨域,则不返回有关信息
Access-Control-Allow-Origin: http://www.myhome.com
//请求端允许使用的方法
Access-Control-Allow-Methods: GET, POST, PUT
//请求端允许使用的请求头参数
Access-Control-Allow-Headers: Custom-Header
//一次预检的有效期
Access-Control-Max-Age: 1728000
Content-Type: text/html; charset=utf-8

预检请求成功后就与简单请求的过程相同了,也会有Origin和Access-Control-Allow-Origin字段,详情可以查看跨域资源共享讲解。我们经常使用nginx来实现负载均衡,但是nginx也可以配置后用来解决前后端分离开发的问题,例如下图

nginx作为解决跨域的工具

使用场景:
如果前后项目部署在多个服务器或者图片使用第三方存储的时候,可能会经常使用跨源。

3.token
3.1来源:

为了记录用户登陆的状态,我们发明了sesion,但是目前为止session已经不能适应时代的发展。原因:

  • 用户量的增加,导致服务器需要记录的数据量变大,服务器不堪重负;
  • 分布式系统中,各服务器不共享session。假如用户在a服务器登录,但是不久之后,请求被发送到b服务器,可以用户的登录记录只存在a中,向b服务器发送的请求就会返回未登录;
  • 移动设备不支持cookie和session;
  • token可以很好的解决跨域问题;

基于以上原因,token应运而生。session的目的就是为了检验登陆状态,而token的设计就带有检验功能。而且token不需要存储在服务器端,可以存储在客户端的任何地方(cookie,localStorage等),只需要在请求时设置在请求头中即可,服务器端会提取请求头中的token并检验,如果通过则说明登录。


token使用流程

使用jwt完成登录校验流程:

  • 用户使用用户名,密码来请求服务器
  • 服务器验证用户的信息,通过验证发送给用户一个token
  • 客户端存储token,并在每次请求时附送上这个token值
  • 服务端验证请求的token,如果验证通过返回数据,否则显示未认证
3.2构成

我们经常使用的token是Json web token(jwt),也是实现上面登录流程的token。
它由三部分构成:第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature)。
①头部有两部分信息:声明类型,也就是是jwt;声明加密的算法 例如 HMAC SHA256

{
 'typ': 'JWT',
 'alg': 'HS256'
}

②载荷就是存放有效信息的地方。包含三个部分:标准中注册的声明;公共的声明;私有的声明。其中
标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
{
 "sub": "1234567890",
 "name": "John Doe",
 "admin": true
}

note:公共的声明和私有的声明可以添加其他任何信息,但是,token中的信息可以被解密出来,所以不建议存放敏感信息
③第三部分是一个签证信息(signature),这部分需要将base64加密后的header和base64加密后的payload连接组成的字符串(头部在前),然后通过header中声明的加密方式进行加盐secret组合加密,就构成了jwt的第三部分

signature = encrypt(base64(header) + base64(payload), salt);

最后,jwt=base64(header)+","+base64(payload)+","+signature。形如:

//base64(header),base64(payload),signature;
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

note:需要注意的是由于jwt的header和payload只使用了base64加密,这种方式是不安全的,所以不建议将敏感信息存储在token中;另外为了防止第三方恶意伪造token,网站会加密token后返回给客户端,如果客户端请求的token解密失败,则说明被修改或者伪造。

3.3token的使用

token的使用场景非常广泛,除了上面提到的jwt,还有pc端二维码登录,第三方授权登录等。
我们的app在登陆一次之后就在需要登录了,即使关机的情况下也不需要,其实就用到了上面的jwt。
pc端二维码登录
主要分为三步:1.pc端生成登陆二维码;2.用户使用app扫描;3.用户点击app上的确认登陆。具体步骤分为

pc端扫码登陆流程

①PC端向服务端发起请求,告诉服务端,我要生成用户登录的二维码,并且把PC端设备信息也传递给服务端
②服务端收到请求后,它生成二维码ID,并将二维码ID与PC端设备信息进行绑定,然后把二维码ID返回给PC端
③PC端收到二维码ID后,生成二维码(二维码中肯定包含了ID)
④为了及时知道二维码的状态,客户端在展现二维码后,PC端不断的轮询服务端,比如每隔一秒就轮询一次,请求服务端告诉当前二维码的状态及相关信息
⑤⑥用户用手机去扫描PC端的二维码,通过二维码内容取到其中的二维码ID
再调用服务端API将移动端的身份信息与二维码ID一起发送给服务端
⑦服务端接收到后,它可以将身份信息与二维码ID进行绑定,生成临时token。然后返回给手机端
⑧因为PC端一直在轮询二维码状态,所以这时候二维码状态发生了改变,它就可以在界面上把二维码状态更新为已扫描
⑨⑩手机端在接收到临时token后会弹出确认登录界面,用户点击确认时,手机端携带临时token用来调用服务端的接口,告诉服务端,我已经确认
服务端收到确认后,根据二维码ID绑定的设备信息与账号信息,生成用户PC端登录的token
最后:这时候PC端的轮询接口,它就可以得知二维码的状态已经变成了"已确认"。并且从服务端可以获取到用户登录的token。到这里,登录就成功了,后端PC端就可以用token去访问服务端的资源了。具体信息可以查看二维码登录简介

第三方授权登录
我们在登陆一些网站时,经常为了方便使用微信或者微博账号登录,这样不仅不需要注册大量信息,而且还可以减少撞库的风险。这种方式登录第三方的行为就使用了token。

撞库:我们经常为了方便将多个网站的密码设置成相同或者相似的,如果一个网站的密码泄露时,黑客可能会得到会得到我们很多网站的权限,这种行为叫做撞库撞库简介

第三方登录的流程(以OAuth的授权码模式为例):

Third-party application:第三方应用
HTTP service:服务提供商,本文中指的是微信或者微博
Resource Owner:用户/资源拥有者,本文指的是在微信中注册的用户
Authorization server:认证服务器,在资源拥有者授权后,向客户端授权(颁发 access token)的服务器
Resource server:资源服务器,务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器

第三方授权登录流程

具体流程(详细流程可以查看OAuth第三方授权登录):
①用户登录第三方网站,并使用微信授权的方式登录;
②这是会跳转到微信的登录授权页面,跳转地址形如:

https://www.weixin.com/oauth/authorize?response_type=code&client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&scope=read
  • response_type参数表示授权类型(code)
  • client_id参数让微信知道是哪个第三方应用在请求,每个支持微信授权登录的第三方都需要先到微信平台申请,得到相应的client_id和client_secert
  • redirect_uri参数是微信接受或者拒绝授权后的跳转网址,这里我们假设是https://music.163.com/callback
  • scope参数表示要求的授权范围(这里是只读)

③如果用户同意授权就会从微信跳转到redirect_uri所指向的地址,并且会传回授权码,请求形如:

https://music.163.com/callback?code=AUTHORIZATION_CODE

④第三方网站就会使用这个授权码(code)和第三方身份信息(client_id,client_secret)向授权服务器申请token,请求形如:

https://b.com/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&
grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL
  • client_id参数和client_secret参数用来确认第三方应用的身份(client_secret参数是保密的,因此只能在后端发请求)
  • grant_type参数的值是AUTHORIZATION_CODE,表示采用的授权方式是授权码
  • code参数是上一步拿到的授权码
  • redirect_uri参数是微信生成token后的回调网址

微信方会生成授权token,并通过redirect_uri返回一段json

{    
 "access_token":"ACCESS_TOKEN",
 "token_type":"bearer",
 "expires_in":2592000,
 "refresh_token":"REFRESH_TOKEN",
 "scope":"read",
 "info":{...}
}

其中access_token就是授权token,expires_in是令牌的有效期。最后第三方网站就可以使用该token请求用户的微信数据。
更新令牌:令牌的有效期到了,如果让用户重新走一遍上面的流程会比较麻烦。所以我们设计了自动更新令牌的方法,需要用到上一步的REFRESH_TOKEN,更新令牌请求形如:

https://b.com/oauth/token?grant_type=refresh_token&client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&refresh_token=REFRESH_TOKEN
  • grant_type参数为refresh_token表示要求更新令牌
  • client_id参数和client_secret参数用于表示第三方应用身份
  • refresh_token参数就是用于更新令牌的令牌,也就是上面返回的json段中的REFRESH_TOKEN

参考:
localStorage使用总结
HTML5前端数据库——Web SQL Database
深入理解Cookie
数据库 IndexedDB 入门教程
IndexedDB
http请求头
session, token

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容