带你了解Cookie、Session和Token

​ 啥是 cookie?啥又是 sessiontoken 又是咋用?这些问题在蛮久之前困扰了我好久。依照后台的讲解来说我理解的 sessioncookie 没什么两样,而 token 的话好像还是,依然是默认携带的东西交给后台做处理。但是!作为一个前端仍然有必要了解其后的机制!

​ 作为三大主角中第一个出场的 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 集中存储在一个地方,所有的机器都由这台服务器进行验证。”倒是没什么问题,但是把所有鸡蛋放在同一个篮子里,既增加了单点失败的可能性,又是极大的不稳定因素,万一没拿稳岂不是全没了。最后的解决办法还是备份,可这么个小东西搞到这个份儿上的确让人有点烦躁。

session集中存储模式

Token

引子

服务器:为什么只让我存储 session?难道没有更好的办法了吗?

​ 不行的哦老弟,因为用户凭证由你所生成,而相应的也应该由你进行验证。所以只能……

​ 不对,我们解决的是如何判断用户身份问题,而要做的就是后台可以生成一个唯一身份标识并且验证。完全可以绕开存储的问题

​ 既然可以绕开那么我们怎么进行呢?关键点就是验证

​ 既然后台不进行保存只有前端进行存储,那么自然,作为一个搞软件的都知道,空间可以换时间,那么反过来,时间亦可以换空间。至此,我们的主角 Token 正式出场

生成

​ 基于时间换空间的理念,我们此时不在 Server 端进行数据的存储,而只是占用资源进行验证。

​ 在用户登录之后我们会由 Server 进行验证,通过则将该用户改为登录态。随后为其生成 TokenToken 的生成事实上需要以下内容:

  • userid
  • 时间戳
  • 过期时间

Server 端会用 特定的密钥userid 和 时间戳以及过期时间进行加密,并将结果进行一次 hash 处理,以保证其位数(长度)统一。这就是我们 Token 的 “数字签名”。

而真正的 Token 则是我们上述生成的 “数字签名” 加上 “原料” —— userid & 时间戳。

验证

​ 学过 HTTPS 的 boys 应该知道数字签名这东西(没学过的去看一下去!),那么我们也可以!后台制定一套加密规则,将 sessionid 通过密钥加密后生成一个签名,附带着明文的 sessionid 发送给浏览器,而浏览器则继续以 cookie 的形式进行存储。而浏览器向服务器发送请求时则会在 HTTP 中的 Header 中附带 cookie,而浏览器拿到之后则使用其发送而来的 sessionid 进行密钥加密,完成后和一同发送而来签名对比,相同则身份一致,否则身份错误,将其登陆状态下掉,这就是我们的 Token~

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 也是有有效期的,但是这个有效期就可以长一点了,比如,以天为单位的时间。