啥是 cookie
?啥又是 session
?token
又是咋用?这些问题在蛮久之前困扰了我好久。依照后台的讲解来说我理解的 session
和 cookie
没什么两样,而 token
的话好像还是,依然是默认携带的东西交给后台做处理。但是!作为一个前端仍然有必要了解其后的机制!
Cookie
作为三大主角中第一个出场的 boy cookie
无疑成为了众多前端小伙伴的第一个交互时所用到的对象。凡是拥有身份系统的应用中为了弥补 HTTP 无状态这一缺点(暂时先将其归为缺点吧),则是通过后台向前端传递的一个 kv 形式的字段 ,并在向后台发送请求时携带。而后台系统则通过验证前端所携带而来的 cookie
来进行身份的识别,如此一来皆大欢喜,皆大欢喜!
真的皆大欢喜了吗?
首先,作为一个以 key-value 形式存储在本地的重要身份识别字段自然无法避免的被不法分子利用,从而造成用户身份及信息的泄露,严重的甚至危害用户的人身和财产安全,所以为了解决此问题在 HTTP 字段中我们可以通过加设 http-only
字段对其进行限制,禁止脚本获取。另外由于同源策略的限制在每个域下的 cookie
都独立存储,不会对其他域的 cookie
造成影响,如此一来就可以在请求发送之后完成用户身份的校验啦!
那为什么会有 session
这玩意儿的存在?
Session
引子
其实本质上来说 cookie
只是一个 “餐后甜点”,其作用就是本地存储,作用范围仅限于浏览器,而 session
才是服务器之间的硬通货。
创始之初 web 网页就是让大家浏览信息所用,而直到交互式应用的出现才使得 web 应用焕发了其真正的生机。如上所述,HTTP 无状态的特性此时就成了缺点,服务端无法识别请求发送者的具体身份,相应的也就无法识别,于是 session
应运而生。
详情
session
这东西说来也简单,服务器生成一个随机的字符串给浏览器,你的我的他的都不一样,给服务器发请求的时候带上,这样就完成了身份的验证,而咱们的客户端(浏览器)只需要存一个 cookie
而已,很棒,不是吗?
“不是的!”,服务器如是说道。
想想也对,咱们就存一个 cookie
,而人家得存咱们所有人的 cookie
,这事儿搁谁身上都不高兴啊。为了解决这种问题服务器想到了另一个办法:找兄弟帮我分担——一台不够,那咱们加一台。
可是兄弟毕竟是兄弟,你这两边各存一半 session
,那人家请求过来该找谁?于是乎 session sticky
作为一个小伎俩被支出来,是谁验证的就一直谁管理,咱们两边分开来搞。
那万一有一台挂掉了怎么办?还是得数据移植啊,只有备份才能保证其安全性。可是两台还好,三台四台五台六台怎么办?这个是数量级级别的增长呀。
Memcached:“我们可以把 session
集中存储在一个地方,所有的机器都由这台服务器进行验证。”倒是没什么问题,但是把所有鸡蛋放在同一个篮子里,既增加了单点失败的可能性,又是极大的不稳定因素,万一没拿稳岂不是全没了。最后的解决办法还是备份,可这么个小东西搞到这个份儿上的确让人有点烦躁。
Token
引子
服务器:为什么只让我存储
session
?难道没有更好的办法了吗?
不行的哦老弟,因为用户凭证由你所生成,而相应的也应该由你进行验证。所以只能……
不对,我们解决的是如何判断用户身份问题,而要做的就是后台可以生成一个唯一身份标识并且验证。完全可以绕开存储的问题!
既然可以绕开那么我们怎么进行呢?关键点就是验证
既然后台不进行保存只有前端进行存储,那么自然,作为一个搞软件的都知道,空间可以换时间,那么反过来,时间亦可以换空间。至此,我们的主角 Token
正式出场
生成
基于时间换空间的理念,我们此时不在 Server
端进行数据的存储,而只是占用资源进行验证。
在用户登录之后我们会由 Server
进行验证,通过则将该用户改为登录态。随后为其生成 Token
。Token
的生成事实上需要以下内容:
userid
- 时间戳
- 过期时间
Server
端会用 特定的密钥 对 userid
和 时间戳以及过期时间进行加密,并将结果进行一次 hash
处理,以保证其位数(长度)统一。这就是我们 Token
的 “数字签名”。
而真正的 Token
则是我们上述生成的 “数字签名” 加上 “原料” —— userid
& 时间戳。
验证
学过 HTTPS 的 boys 应该知道数字签名这东西(没学过的去看一下去!),那么我们也可以!后台制定一套加密规则,将 sessionid
通过密钥加密后生成一个签名,附带着明文的 sessionid
发送给浏览器,而浏览器则继续以 cookie
的形式进行存储。而浏览器向服务器发送请求时则会在 HTTP 中的 Header
中附带 cookie
,而浏览器拿到之后则使用其发送而来的 sessionid
进行密钥加密,完成后和一同发送而来签名对比,相同则身份一致,否则身份错误,将其登陆状态下掉,这就是我们的 Token
~
好处
- 从根本上防止 CSRF 攻击
- 不必担心状态(登陆状态)数据迁移
无状态Token带来的问题
虽然我们无需对已登录状态的 user
进行存储,但是由于统一由 Server
端验证,所以我们需要手动存储 Token
未过期却主动注销 的账号。为了让 Server
端不进行存储我们可以约定一旦注销则自动删除本地存储中的 Token
。如此一来则发向服务端的请求只有验证登录态和更改为登录态两种情况,完美解决。
后续
后期在掘金上看博客时看到了 边城 的博客,其中有对于 Token
登录态设置过期时间的讨论。 觉得受益匪浅,故搬运至此。
正常我们 Server
端进行设置其实本身无可厚非,无论时间长短都无所谓,但是有个问题。不论过期时间设置长短都有可能存在这种问题:用户操作过程中登录态过期
而此时大部分人采用的是当年 Session
的办法:Server
端预存 Token
状态,用户每次操作都会自动更新(推迟)Token
过期时间。但是同时也衍生出另外的问题:SPA 的出现使得请求的发送频率变高了很多,如此一来后端的运算量激增,代价过大。所以为了提高效率通常会将 Token 的过期时间存储在缓存或内存中。
而另一种方案是 Refresh Token
。这种方案中,服务端不需要刷新 Token
的过期时间,一旦 Token
过期,就反馈给前端,前端使用 Refresh Token
申请一个全新 Token
继续使用。这种方案中,服务端只需要在客户端请求更新 Token
的时候对 Refresh Token
的有效性进行一次检查,大大减少了更新有效期的操作,也就避免了频繁读写。当然 Refresh Token
也是有有效期的,但是这个有效期就可以长一点了,比如,以天为单位的时间。