登录凭证cookie、session、token的Koa实现

一.cookie

 1.1. 认识cookie

1.2. cookie常见属性

1.3. cookie客户端设置

  • 在浏览器中通过js设置cookie(在开发中很少使用)
  • 没有设置max-age时, 是一个内存cookie, 浏览器关闭时会消失
  • 设置max-age时, 是一个硬盘cookie, 只能等到过期时间到达的时候, 才会销毁
document.cookie = "name=why;max-age=30;"
document.cookie = "age=18;max-age=60;"

1.4 cookie服务器设置

  • 服务器设置cookie

Koa 中默认支持直接操作 cookie

  • 浏览器接受cookie, 并且客户端保存

  • 在之后的作用域其他网络请求, 会自动携带cookie

  • 服务器获取cookie, 并且验证cookie

const Koa = require('koa')
const KoaRouter = require('@koa/router')

const app = new Koa()

const userRouter = new KoaRouter({ prefix: '/users' })

/**
 * 1.服务器设置cookie
 * 2.客户端(浏览器)保存cookie
 * 3.在同一个作用域下访问服务器, 自动携带cookie
 * 4.服务器验证客户端携带的cookie
 */
userRouter.get('/login', (ctx, next) => {
  // 在服务器中为登录的客户端, 设置一个cookie
  ctx.cookies.set('slogan', 'ikun', {
    maxAge: 60 * 1000 * 5
  })

  ctx.body = '登录成功~'
})

userRouter.get('/list', (ctx, next) => {
  // 验证用户的登录凭证: 携带口号 ikun
  const value = ctx.cookies.get('slogan')
  console.log(value)
  if (value === 'ikun') {
    ctx.body = `user list data~`
  } else {
    ctx.body = `没有权限访问用户列表, 请先登录~`
  }
})

app.use(userRouter.routes())
app.use(userRouter.allowedMethods())

app.listen(8000, () => {
  console.log('服务器启动成功~')
})

二、session

 2.1 服务器使用session

  • 基于cookie

  • cookie中信息和内容进行加密处理, 加密签名

  • 早期登录凭证: cookie+session

const Koa = require('koa');
const KoaRouter = require('@koa/router');
const koaSession = require('koa-session');

const app = new Koa();

const userRouter = new KoaRouter({ prefix: '/users' });

const session = koaSession (
  {
    key: 'sessionid', //cookie的key
    maxAge: 5 * 1000, //过期时间
    httpOnly: true, //不允许通过js获取cookie
    rolling: true, // 每次响应时,刷新session的有效期
    signed: true, // 是否使用signed签名认证,防止数据被篡改
  },
  app
);

// 加盐操作
app.keys = ['aaa', 'bbb', 'why', 'kobe'];
app.use(session);

userRouter.get('/login', (ctx, next) => {
  // 在服务器中为登录的客户端, 设置一个cookie
  ctx.session.slogan = 'ikun';

  ctx.body = '登录成功~';
});

userRouter.get('/list', (ctx, next) => {
  // 验证用户的登录凭证: 携带口号 ikun
  const value = ctx.session.slogan;
  console.log(value);
  if (value === 'ikun') {
    ctx.body = `user list data~`;
  } else {
    ctx.body = `没有权限访问用户列表, 请先登录~`;
  }
});

app.use(userRouter.routes());
app.use(userRouter.allowedMethods());

app.listen(8000, () => {
  console.log('服务器启动成功~');
});

2.2  cookie+session缺点

三、 token的颁发和验证

3.1  jsonwebtoken 生成token

const jwt = require('jsonwebtoken') 
const token = jwt.sign(payload, secretkey, {
    expiresIn: 60
  })

 ​​​​​​

JWT生成的 Token 由三部分组成:

header

  • alg:采用的加密算法,默认是 HMAC SHA256 (HS256 ),采用同一个密钥进行加密和解密(对称加密)
  • typ:JWT ,固定值,通常都写成 JWT 即可;
  • 会通过base64Url 算法进行编码;


payload

  • 携带的数据,比如我们可以将用户的id 和 name 放到 payload 中;
  • 默认也会携带   iat (issued at ),令牌的签发时间
  • 我们也可以设置过期时间:exp (expiration time)
  • 会通过base64Url 算法进行编码


signature 

  • 设置一个secretKey ,通过将前两个的结果合并后进行 HMACSHA256 的算法;
  • HMACSHA256(base64Url(header)+.+base64Url(payload),secretKey);
  • 但是如果secretKey 暴露是一件非常危险的事情,因为之后就可以模拟颁发 token ,也可以解密 token

3.2 获取客户端携带的token

  const authorization = ctx.headers.authorization
  const token = authorization.replace('Bearer ', '')
  console.log(token)

  postman 模拟客户端携带token

 实际 token  在 headers.authorization 字段中

3.3 验证 token,获取用户信息

const result = jwt.verify(token, secretkey)

3.4 缺点: 对称加密 

默认使用的HS256 加密算法是对称加密算法,加密和解密都是同一个密钥,一旦密钥暴露就是非常危险的事情:

  • 比如在分布式系统中,每一个子系统都需要获取到密钥;
  • 那么拿到这个密钥后这个子系统既可以发布另外,也可以验证令牌;
  • 但是对于一些资源服务器来说,它们只需要有验证令牌的能力就可以了;

  完整实现代码

const Koa = require('koa')
const KoaRouter = require('@koa/router')
const jwt = require('jsonwebtoken')

const app = new Koa()

const userRouter = new KoaRouter({ prefix: '/users' })

const secretkey = 'aaabbbccxxxx'

userRouter.get('/login', (ctx, next) => {
  // 1.颁发token
  const payload = { id: 111, name: 'why' }
  const token = jwt.sign(payload, secretkey, {
    expiresIn: 60   //过期时间
  })

  ctx.body = {
    code: 0,
    token,
    message: '登录成功, 可以进行其他的操作'
  }
})

userRouter.get('/list', (ctx, next) => {
  // 1.获取客户端携带过来的token
  const authorization = ctx.headers.authorization
  const token = authorization.replace('Bearer ', '')
  console.log(token)

  // 2.验证token
  try {
    const result = jwt.verify(token, secretkey)
    
    ctx.body = {
      code: 0,
      data: [
        { id: 111, name: 'why' },
        { id: 111, name: 'why' },
        { id: 111, name: 'why' },
      ]
    }
  } catch (error) {
    ctx.body = {
      code: -1010,
      message: 'token过期或者无效的token~'
    }
  }
})

app.use(userRouter.routes())
app.use(userRouter.allowedMethods())

app.listen(8000, () => {
  console.log('服务器启动成功~')
})

3.5. 非对称加密颁发令牌

可以使用非对称加密,RS256

  • 私钥( private key ):用于发布令牌
  • 公钥( public key ):用于验证令牌

我们可以使用 openssl 来生成一对私钥和公钥:
Mac 直接使用 terminal 终端即可;
Windows默认的 cmd 终端是不能直接使用的,建议直接使用 git bash 终端;

openssl
>
genrsa -out private.key 1024
>
rsa -in private.key -pubout -out public.key
  • 读取私钥和公钥
const fs = require('fs')
const privateKey = fs.readFileSync('./keys/private.key')
const publicKey = fs.readFileSync('./keys/public.key')
  •  颁发 token ,用私钥 privateKey 进行加密
const token = jwt.sign(payload, privateKey, {
    expiresIn: 60,   //token有效时间
    algorithm: 'RS256'  //加密算法
  })
  • 验证 token 用公钥 publickey 进行解密
const result = jwt.verify(token, publicKey, {
      algorithms: ['RS256']
    })

 完整代码

const fs = require('fs')
const Koa = require('koa')
const KoaRouter = require('@koa/router')
const jwt = require('jsonwebtoken')

const app = new Koa()

const userRouter = new KoaRouter({ prefix: '/users' })

const privateKey = fs.readFileSync('./keys/private.key')
const publicKey = fs.readFileSync('./keys/public.key')

userRouter.get('/login', (ctx, next) => {
  // 1.颁发token
  const payload = { id: 111, name: 'why' }
  const token = jwt.sign(payload, privateKey, {
    expiresIn: 60,
    algorithm: 'RS256'
  })

  ctx.body = {
    code: 0,
    token,
    message: '登录成功, 可以进行其他的操作'
  }
})

userRouter.get('/list', (ctx, next) => {
  // 1.获取客户端携带过来的token
  const authorization = ctx.headers.authorization
  const token = authorization.replace('Bearer ', '')
  console.log(token)

  // 2.验证token
  try {
    const result = jwt.verify(token, publicKey, {
      algorithms: ['RS256']
    })
    
    ctx.body = {
      code: 0,
      data: [
        { id: 111, name: 'why' },
        { id: 111, name: 'why' },
        { id: 111, name: 'why' },
      ]
    }
  } catch (error) {
    console.log(error)
    ctx.body = {
      code: -1010,
      message: 'token过期或者无效的token~'
    }
  }
})

app.use(userRouter.routes())
app.use(userRouter.allowedMethods())

app.listen(8000, () => {
  console.log('服务器启动成功~')
})

猜你喜欢

转载自blog.csdn.net/m0_50789696/article/details/129862487