前后端通信中的 Token验证与 RSA加密

JSON Web Token

JSON Web Token(JWT)是一个轻量级的认证规范,这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。其本质是一个token,是一种紧凑的URL安全方法,用于在网络通信的双方之间传递。

JWT流程

  • 客户端登录时向服务端发送请求
  • 服务端验证通过后分发给前端一个 token
  • 客户端存储 token,并在后续请求中带上
  • 服务端验证 token有效性,并返回实际数据

token结构

  • 头部 (Header)
    由两部分组成:令牌类型(即JWT),以及正在使用的签名算法,HMAC SHA256RSA。然后将头部进行base64加密,形成第一部分。
  • 载荷 (Payload)
    负载有效数据的部分,由三部分组成:注册声明,公开声明和私有声明。
    • 注册声明:一组预定义的声明(建议但不强制使用);
    • 公开声明:由使用者自定义(不建议添加敏感信息);
    • 私有声明:提供者和消费者所共同定义的声明(不建议存放敏感信息);
      然后将载荷进行base64加密,形成第二部分。
  • 签名 (Signature)
    这个部分需要 base64 加密后的 header 和 base64 加密后的 payload 使用 . 连接组成的字符串,通过 header 中声明的加密方式进行加盐 secret 组合加密,然后形成第三部分。

使用

  • 安装
yarn add jsonwebtoken
  • 引用
const jwt = require('jsonwebtoken')
const PRIVATEKET = 'hello_jwt' // token 加密密钥

// 签发
const token = jwt.sign( info, PRIVATEKET, {expiresIn: '7d'})
// 验证
// 并提取负载信息
const info = jwt.verify(token, PRIVATEKET) 

/**
 * 业务使用
 */
// koa 登录接口
router.post('/login', async ctx => {
  try {
    const { userName, password } = ctx.request.body || {}
    let results = await mysql.findUserName(userName)
    let userInfo = results[0]
    if (userInfo.password === password) {
      const info = {userName: userInfo.name, userId: userInfo.id }
      // 签发 token, 7天有效期
      const token = jwt.sign( info, PRIVATEKET, {expiresIn: '7d'})
      ctx.response.body = {code: 200, msg: '登录成功', data: {token: token, ...info}}
    } else {
      ctx.response.body = {code: 0, msg: '帐号不存在'}
    }
  } catch(err) {
    ctx.response.body = {code: -1, msg: '服务器异常'}
    throw new Error(err)
  }
})
// 添加数据
router.post('/addComment', async ctx => {
  const token = ctx.get('token') // 获取请求 Header 中 Authorization 值
  if (!token) {
    return ctx.response.body = {code: 0, msg: '未登录'}
  }
  try {
    // 验证 token
    // 验证失败会进入 catch
    let info = jwt.verify(token, PRIVATEKET)
    let {userId, comment} = ctx.request.body || {}
    if(!userId) {
      return ctx.response.body = {code: 0, msg: '数据错误'}
    }
    let {insertId} = await mysql.addComment(userId, comment)
    ctx.response.body = {code: 0, data: {insertId}, msg: 'success'}
  } catch(err) {
    // token 过时或无效
    ctx.response.body = {code: 0, msg: '未登录'}
  }
})

node-rsa

1977年,三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字命名,叫做RSA算法。 -- RSA算法原理(一)阮一峰

非对称加密概念

  • 乙方生成两把密钥(公钥和私钥)。公钥是公开的,任何人都能获取,私钥则是保密的。
  • 甲方获取乙方的公钥, 然后用它对信息进行加密。
  • 乙方得到加密后的信息, 用私钥解密。

安装

yarn add node-rsa

服务端

const NodeRSA = require('node-rsa')
class RSA {
  constructor () {
    this.key = null
    this.publicKey = ''
    this.privateKey = ''
  }
  initRsa () {
    if(!this.key) {
      let key = new NodeRSA({
        b: 512
      });
      //指定加密格式  不改格式得话可能会报错
      // key.setOptions({ encryptionScheme: 'pkcs1' });
      this.key = key
      this.publicKey = key.exportKey('public'); // 公钥
      this.privateKey = key.exportKey('private'); // 私钥
    }
    return this.key;
  }
  decrypt (value) {
    this.key.importKey(this.privateKey)
    return this.key.decrypt(value, 'utf8')
  }
}

// 业务模块
const rsa = new RSA()
// 分发公钥
router.get('/getPublicKey', async (ctx) => {
  rsa.initRsa()
  ctx.response.body = { 
    code: 200, 
    msg: 'success', 
    data: {
      publicKey: rsa.publicKey
    }
  }
})
// 解密
// 沿用上部分案例
router.post('/login', async ctx => {
  try {
    const { userName, password } = ctx.request.body || {}
    let results = await mysql.findUserName(userName)
    let userInfo = results[0]
    if (userInfo) {
      // 解密后
      const truthyPwd = rsa.decrypt(password, 'utf8') 
      if( userInfo.password != truthyPwd ){
        return ctx.response.body = {code: 0, msg: '账号或密码错误'}
      }
      const info = {userName: userInfo.name, userId: userInfo.id }
      // 签发 token, 7天有效期
      const token = jwt.sign( info, PRIVATEKET, {expiresIn: '7d'})
      ctx.response.body = {code: 200, msg: '登录成功', data: {token: token, ...info}}
    } else {
      ctx.response.body = {code: 0, msg: '帐号不存在'}
    }
  } catch(err) {
    ctx.response.body = {code: -1, msg: '服务器异常'}
    throw new Error(err)
  }
})

客户端

import NodeRSA from 'node-rsa'
function entrypt(publicKey, value) {
  // 生成RSA对象
  const key = new NodeRSA({ b: 512 })
  // 导入公钥
  key.importKey(publicKey)
  return key.encrypt(value, 'base64')
}

// 登录业务
async function request_login (name, pwd) {
  try {
    let {publicKey} = await request_getPublicKey() || {}
    let pwdStr = encrypt(publicKey, pwd)
    const res = await api.login(name, pwdStr);
    const { token, userName, userId } = res.data || {};
    return res.data;
  } catch (err) {
    console.log(err);
  }
}
posted @ 2022-07-07 16:09  晨の风  阅读(693)  评论(0编辑  收藏  举报