一文让你看懂cookie, session jwt

585 阅读10分钟

序言

本文主要是针对cookie,session,以及jwt怎么生成的,有什么区别。以及cookie的一些常用的属性进行讨论,同样会总结各种方式的不足之处,如果有不足的地方希望大家可以指正,好了不废话了,我们开始吧!!!!!!!

目的

这里会抛出一个话题,为什么会出现cookie,session呢??? 想要知道这个答案的话,必须联想到http的特性:无状态特征。简单举个例子:就是用户第一次登录一个门户网站,登录成功。第二次进入的时候还需要重新登录,这肯定是不行的。因为http是无状态的,所以我们就需要有个地方记录用户的信息。所以cookie等出现了

案例说明

本案例中我们会粘贴出代码,但是具体的代码还是参照源码。本案例中使用的技术是koa + @koa/router 来给大家做演示

cookie说明

1. 代码案例

route.get('/write', (ctx) => {
  ctx.cookies.set('age', 20, {
    // 设置domain 表示子域a.lihh/ b.lihh 可以共享父域的cookie
    domain: 'lihh.com',
    // 如果设置了httpOnly为true的话 客户端通过document.cookie是获取不到的
    httpOnly: true,
    signed: true
  })
  ctx.body = '设置成功'
})
  • 上述的代码是cookie相关所有的代码,接下来我们会针对domain/ httpOnly/ signed 挨个进行演示,至于的其余的属性我们就不做过多的演示了,因为用的机会其实并不多

domain相关的设置

1. domain 不设置情况下

route.get('/write', (ctx) => {
  ctx.cookies.set('age', 20)
  ctx.body = '设置成功'
})
  • 首先说明一点,我们此时有两个代理站点:a.lihh.com以及b.lihh.com,主要是为了模拟不同域名:
    • 此时运行在域名a.lihh.com:4000/write
    • 图片.png
    • 可以看到默认的domian就是当前的地址
    • 接下来我们看下地址b.lihh.com:4000/read下,其实我们会发现什么都没有。说明当domain设置为当前地址的时候,两个域名间cookie是无法共享的,但是真的不共享吗???请继续往下看 图片.png

2. 设置共享domain

route.get('/write', (ctx) => {
  ctx.cookies.set('age', 20, {
    domain: 'lihh.com',
  })
  ctx.body = '设置成功'
})
  • 通过上述代码我们会发现,我们设置了代码domain: 'lihh.com'. 我们把上述代码的案例运行下看下结果:
    • 我们同样执行域名a.lihh.com:4000/write
    • 图片.png
    • 我们会发现domain中变为了.lihh.com了,接下来我们看下域名b.lihh.com的执行结果
    • 图片.png
    • 当我们运行域名http://b.lihh.com:4000/read的时候,发现cookie已经存在了。而且domain也是.lihh..com。说明他们做到了cookie共享

3. 结论

  • cookie在父子域名之间可以共享,也就是兄弟域名之间是可以共享的
  • 但是两个完全不相干的域名之间是无法做到共享的

httpOnly的一点小知识

1. httpOnly的设置小知识

route.get('/write', (ctx) => {
  ctx.cookies.set('age', 20, {
    domain: 'lihh.com',
    httpOnly: true
  })
  ctx.body = '设置成功'
})
  • 上述的代码中我们添加了配置httpOnly: true,表示 cookie 只能通过 HTTP 协议发送.
  • 还有一旦设置了这个属性,通过API document.cookie 是无法获取该属性的,但是在浏览器的Application面板中还是可以修改的(个人理解:意义不是很大
  • 接下来让我们看下添加配置项后有什么不同
    • 图片.png
    • 其实没有什么特别的不同的地方,就是在属性HttpOnly中标记了√

签名 安全

  • 其实通过上述的演示我们都能看到所有的信息其实都是明文保存的,一点安全性没有。如果数据被人篡改了,当我们接口请求后端的时候,后端其实使用的是错误的数据。
  • 其实从前端来保存数据本身就是不安全的,但是我们的目的不是破罐子破摔,而是尽量让它变得安全起来
  • 基于上述的特点我们的签名就来了,其实我们需要给cookie进行签名,一定程度上来提高数据的安全性
app.keys = ['lihh-test']
route.get('/write', (ctx) => {
  ctx.cookies.set('age', 20, {
    domain: 'lihh.com',
    httpOnly: true,
    signed: true
  })
  ctx.body = '设置成功'
})
  • 我们先看来下在koa中如果设置了签名,出来的效果是什么,还是老样子我们来运行下http://a.lihh.com:4000/write试试
    • 图片.png
    • 通过上述的截图中可以看到koa为我们的属性[age]设置的签名。那签名如何形成的呢
    • 接下来分析下签名如何生成的,到底签名能不能进行修改

1. 签名解析

const crypto = require('crypto')
const values = ['age', 20]
const secret = 'lihh-test'

const toBase64URL = (str) => {
  // 针对转换base64位 如果是= 直接替换位空 如果是+号 直接替换为- 如果是/ 直接替换为_
  return str.replace(/=/gi, '').replace(/+/g, '-').replace(///g, '_')
}

const content = toBase64URL(crypto.createHmac('sha1', secret).update(values.join('=')).digest('base64'))
console.log(content === '1KCzh4f24Jd8gbS2NwB9W7znAo8') // true
  • 这个是上述我们来模拟koa的签名出来的,你会发现其实跟koa的结果是保持一致的。接下来我们来分析下:
    • 需要的包:crypto 我们需要这个包来进行加密算法
    • 上述的代码const values = ['age', 20] 这个是表示需要加密的内容
    • 也许有人会问加密算法可以使用MD5加密吗???首先MD5是公开算法,其次MD5算法是可逆的,显然是不安全的。既然是加密,我们就需要密钥,这里变量secret就表示加密的密钥,这里的密钥是存放到服务器上的,所以是相对安全的
    • 这里我们采用了sha1进行加密(sha1 不可逆)。 请看上述的代码。
    • 那如何验证是对的呢??? 从上述截图中我们可以看到会出现两个key[age], [age.sig]. 我们就是拿到了原始的key/value 根据sha1重新进行加密。拿加密后的新值跟coookie中的[age.sig]的值进行判断,是否一致

其余属性

  • expires 设置cookie的时效性
  • path 设置cookie是否针对某个路径进行cookie设置, 意义不大
  • ...

session说明

session演示的代码可以参照上述具体的地址,这里不做截图演示。但是会说出大致原理,其实session很简单

// 密钥
const secret = 'zfsecret'
app.keys = [secret]
const router = new Router()

let cardName = 'lihh-test' // 店铺名字
// 变量内存 正式环境一般都是redis
let session = {}
router.get('/wash', async function (ctx) {
  // 也是存储到cookie 加盐算法 保护id
  const hasVisit = ctx.cookies.get(cardName, { signed: true })

  // 判断cookie中是否存在
  if (hasVisit && session[hasVisit]) {
    // 必须保证你的卡是我的店的
    session[hasVisit].mny -= 100
    ctx.body = '恭喜你消费了 ' + session[hasVisit].mny
  } else {
    const id = uuid.v4() //冲500
    session[id] = { mny: 500 }

    // 给cookie中设置内容
    ctx.cookies.set(cardName, id, { signed: true })
    ctx.body = '恭喜你已经是本店会员了 有500元'
  }
})
  • session的实现过程请求看下面的截图:

session实现过程.png

  • 如果能看懂上述的截图就不用看下面的文字描述了

    • 如果用户第一次登录的话,用户访问后端,后端会根据查询出来的用户信息,保存到一个地方,而且以一个唯一的值作为对象key。其实就是身份凭证,就是后来我们所说的sessionId
    • 服务器端进行响应,客户端将sessionId进行保存
    • 往后的请求浏览器都会携带sessionId来验证,表明了用户身份
  • 其实session的实现原理还是很简单,用一句大白话来说就是:给每个人分配一个唯一的id(这个id在用户表中可以查询到用户信息),表示身份凭证,以后每次来请求带携带着id就够了

  • 其实我们可以做的还有几个事情:

    • session的携带有个方面:第一个是可以放到cookie中。第二个可以放到请求header中
    • 为了更加的安全性,我们可以给sessionId进行签名。一定程度上保障数据的安全性
  • 接下来我们来看下关于sessionId的运行结果: 图片.png

  • 从上述截图中我们其实是使用了uuid作为sessionId,同时对uuid进行签名

jwt 说明

其实我们都知道,一种新鲜技术的出现往往是为了弥补另一种技术的不足。jwt也是如此。关于cookie/ session/ jwt横向对比后面我们会做结论。但是jwt的出现确实为了弥补sesson不足

1. 什么是jwt

  • jwt全称是Json Web Token. 是用户认证的一种方式。

2. token详解

const head = this.toBase64({ typ: 'JWT', alg: 'HS256' })
const content = this.toBase64(info)
const sign = this.sign([head, '.', content].join(''), secret)

// 加密类型(sha256) + 加密内容 + 通过盐值来生成签名
return head + '.' + content + '.' + sign
  • 其实token的实现内容有很多,上述只是粘贴出了一部分代码,具体的代码请参照上述gitHub地址
  • 其实关于jwt并不是很好演示,这里我们就先说下大致的实现过程再结合token如何生成的来详细讲解下
    • 个人觉得token 以及session在一定程度上有点类似。session中有sessionId以及用户信息。但是token中存在所谓的“令牌”,同样,“令牌”是唯一的,表示的身份信息
    • 这里用大白话来解释下:使用用户id通过sha256加密,结合一些别的内容得到的值就是身份令牌
    • 下面来演示下得到的token的结果:图片.png
    • 其实token分为三部分(下面我们回举例说下):
      • 加密类型
      • 加密内容(我们一般可以使用id)
      • 以及签名

1. 加密类型

  • 我们token采用的加密类型是sha256. 最后通过base64位进行转换,例如如下代码:
Buffer.from(JSON.stringify({ typ: 'JWT', alg:'HS256'})).toString('base64')
  • 其实这里的对象不变的,就是转换为了base64位

2. 加密内容

  • 加密内容就更加简单了,就是把上述类型直接替换位内容就行。为了具有唯一性,可查询性我们一般都是用id
toBase64({id: 'XXXXX'})

3. 签名

  • 其实签名跟上述cookie等签名的原理大致是一致的,但是这里只不过使用了sha256来进行签名认证
crypto.createHmac('sha256', secret).update(content).digest('base64')
// secret 相当于密钥
// content 内容其实就是[加密类型, 加密内容].join("") 的值

4. 最后

  • 所以看到上述的截图的时候其实token分为三部分,分别是:加密类型 + 加密内容 + 签名,用点进行相连。
  • 关于token的验证:其实就是用.分割字符串。拿前两个值再次进行签名。判断是否跟第三个值是否一致

大总结

  • cookie优点:
    • 使用灵活,简单,方便
    • 传输方便,不需要我们操作
  • cookie缺点:
    • 保存前端明文
    • 无法进行跨域
  • session优点:
    • 相对于JWT,可以主动清除session
    • session保存在服务器,相对安全
    • 结合cookie进行传输,相对容易点
  • session缺点:
    • cookie + session在跨域场景表现不好
    • 如果是分布式部署,需要做多机共享
    • 基于cookie的机制很容易被攻击
    • 需要服务端支持保存sessionId
  • token优点:
    • 可以基于多种方式(cookie/ header)来进行传输
    • 在分布式部署的时候,只需要获取到加密内容就行
    • 不需要服务端额外的保存数据
  • token缺点:
    • 如果放置到header中,加密的内容越多,请求头服务越重

赠送

通过上述的分析,大家应该都知道什么叫cookie/ session/ token了。接下来赠送几篇文章登录相关机制 大家可以看下

结束

说了这么多了,如果不明白的,或是讲解不对的地方希望大家多多指正。还是老样子接下来我们坐下自我介绍 个人logo