vue3+nodejs基于RSA加密的身份认证(token的组成及使用)

7 篇文章 1 订阅

nodejs学习

一、NodeJs中

1、Token的组成
Token = header.payload.signature 
  	  = base64(header).base64(payload).RSA(base64(header).base64(payload))
  • Header(标头)

组成:令牌的类型(即JWT)和所使用的签名算法,签名算法例如HMAC SHA256或RSA。

let header = { // token标头信息
  "alg": "RSA", // 签名算法RSA
  "typ": "JWT" // 统一jwt令牌类型
};
  • Payload(有效负载)

有关实体(通常是用户)和其他数据的声明。

let payload = { // token有效负载,可自定义
  "nbf": new Date().getTime(), // 生效时间当前系统时间毫秒数
  "uid": uid, // 用户uid
  "lastTime": 60 * 1000 * tokenTimeout,//有限时间
}
  • Signature(签名)

header和payload都是Base64编码过的,中间⽤.隔开,Signature就是使用加密算法将header和payload合起来做签名,RSA(base64(header).base64(payload)),签名的作用是保证 JWT 没有被篡改过

2、Signature的生成

Signature = RSA(base64(header).base64(payload)),将base64编码过的header和payload拼接在一起,用RSA加密算法进行加密

  • RSA加密算法

    RSA加密算法,是生成一对密钥,分别为公钥、私钥,成对存在。用其一加密,另一个解密。

    token一般使用 私钥加密,公钥解密 的方式来进行签名认证

1.公钥加密,私钥解密

公钥被多人持有,对于公钥加密的信息,只有私钥才能解密,从而实现了数据可以保密的到达拥有私钥的一方。即使被第三方截取,也无法解密。

2.私钥加密,公钥解密

一般被用于数字签名。数字签名是用于防篡改和防止假冒的,因为只有一人拥有私钥。甲方通过私钥对数据进行签名,乙方通过甲方的公钥验证签名,如果成功,说明确实是甲方发来的,并且数据没有被修改。一旦相反,公钥是公开的,大家都能做签名,就没意义了。

  • RSA安装

    npm i node-rsa
    
  • RSA生成密钥对存文档

    //生成公钥私钥存文档,给之后加密解密使用
    //通过 node xxx.js 来运行文件
    const NodeRSA = require('node-rsa');
    //执行文件,密钥存文档后就可以注释以下代码
    let key = new NodeRSA({b:1024})
    var publicDer = key.exportKey('public');
    var privateDer = key.exportKey('private');
    console.log('公钥:',publicDer);
    console.log('私钥:',privateDer);
    

在这里插入图片描述

const NodeRSA = require('node-rsa');
const fs = require('fs')
const path = require('path')
//读取私钥加密
const rsaS = fs.readFileSync(path.join(__dirname,'../config/private.pem'));
let key = new NodeRSA(rsaS)
let signature = key.encryptPrivate(base64编码过的header以及payload,'base64')
console.log(signature)
//读取公钥解密
const rsaG = fs.readFileSync(path.join(__dirname,'../config/public.pem'));
let key = new NodeRSA(rsaG)
let designature = key.decryptPublic(signature, 'utf8')
console.log(designature)
3、token的生成与检验
  • 工具类

    base64编码、解码(处理payload以及header)

    RSA生成加密解密key(处理signature)

class Rsa{
    base64(str) { //base64加密方法
        if (typeof str !== 'string') {
            str = JSON.stringify(str);
        }
        return Buffer.from(str).toString("base64");
    }
    base64decode(str=''){//base64解密
        return Buffer.from(str, "base64").toString('utf-8');
    }
    nodersaS(){//RSA 私钥生成加密key
        return new NodeRSA(rsaS)
    }
    nodersaG(){//RSA 公钥生成解密key
        return new NodeRSA(rsaG)
    }
}
  • 生成token
const rsa = new Rsa()//实例化rsa类
//生成token
function getToken(uid){
    let key = rsa.nodersaS()
    let header = { // token头部信息
        "sec": "RSA", // 密码算法RSA
        "type": "JWT" // 统一jwt类型
    };
    let payload = { // 定义数据信息, 可以自己定义, 但是不要放机密信息
        "nbf": new Date().getTime(), // 生效时间当前系统时间毫秒数
        "uid": uid, // 用户uid
        "lastTime": 60 * 1000 * tokenTimeout,//tokenTimeout放在全局配置config里,方便修改
    }
    let sign = key.encrypt(rsa.base64(header)+'.'+rsa.base64(payload),'base64')
    const token = rsa.base64(header)+'.'+rsa.base64(payload)+'.'+sign

    return token
}

//测试调用 
//console.log(getToken(33))
  • token的检验
function checkToken(token){
    let arr = token.split('.');
    let key = rsa.nodersaG()
    let payload;
    if(arr.length===3 && arr[2]){
        const token = key.decryptPublic(arr[2], 'utf8')
        let headpay = token.split('.')
        if(arr[0]===headpay[0] && arr[1]===headpay[1]){
            payload = JSON.parse(rsa.base64decode(arr[1]))
            if(new Date().getTime() - payload['nbf'] < payload['lastTime']){
                return {code:true}
            }else{
                return {code:false,msg:'token过期'}
            }
        }else{
            return {code:false,msg:'token有误'}
        }
    }else{
        return {code:false,msg:'token有误'}
    }
}
//测试调用 
//let token = getToken(33);
//console.log(checkToken(token))
4、token的使用
  • 全局拦截器

    要放在 声明路由的上面 才会生效

//app.js
const {checkToken} = require('../utils/RSA')
// 拦截器  要放在声明路由的上面
let notcheck = ['/login','/register'] //此数组中路由不用检测是否携带token
app.use(async (req, res, next) => {
  if(notcheck.indexOf(req.path) > -1){
    next()
  }else{
    if (!req.headers.authorization) {
        res.send(MError("请设置请求头,并携带验证字符串"));
    } else {
      let result = await checkToken(req.headers.authorization)
      if(result.code){
        next();
      }else{
        res.send(MError(result.msg));
      }
    }
  }
});
  • 登陆接口派发
router.get('/login',async (req,res,next)=>{
  let {name,password} = req['query']
  if(!name || !password){
    res.send(MError('账号信息不存在'))
    return
  }
  const result = await db.select(`SELECT * FROM user WHERE name = '${name}'`)
  if( result && password=== result[0].password){
    let token = await getToken(result.id)
    res.send(Success({...result,token}));
    return
  }else{
    res.send(MError('用户名或密码错误'))
    return
  }
})
5、示例
  • app.js
//app.js
const {checkToken} = require('../utils/RSA')
const {NOauthor} = require('./utils/result')
// 拦截器  要放在声明路由的上面
let notcheck = ['/login','/register'] 
app.use(async (req, res, next) => {
  if(notcheck.indexOf(req.path) > -1){
    next()
  }else{
    if (!req.headers.authorization) {
        res.send(NOauthor("请设置请求头,并携带验证字符串"));
    } else {
      let result = await checkToken(req.headers.authorization)
      if(result.code){
        next();
      }else{
        res.send(NOauthor(result.msg));
      }
    }
  }
});
  • utils/rsa.js
const NodeRSA = require('node-rsa');
const fs = require('fs')
const path = require('path')
const {tokenTimeout} = require('../config/index')

// 生成公钥私钥存文档,给之后加密解密使用
// let key = new NodeRSA({b:1024})
// var publicDer = key.exportKey('public');
// var privateDer = key.exportKey('private');
// console.log('公钥:',publicDer);
// console.log('私钥:',privateDer);

//使用私钥加密,使用公钥解密
const rsaS = fs.readFileSync(path.join(__dirname,'../config/private.pem'));//读取私钥
const rsaG = fs.readFileSync(path.join(__dirname,'../config/public.pem'));//读取公钥
class Rsa{
    base64(str) { //base64加密方法
        if (typeof str !== 'string') {
            str = JSON.stringify(str);
        }
        return Buffer.from(str).toString("base64");
    }
    base64decode(str=''){//base64解密
        return Buffer.from(str, "base64").toString('utf-8');
    }
    nodersaS(){//私钥加密key
        return new NodeRSA(rsaS)
    }
    nodersaG(){//公钥解密key
        return new NodeRSA(rsaG)
    }
}
const rsa = new Rsa()
exports.getToken = async (uid)=>{
    let key = rsa.nodersaS()
    let header = { // token头部信息
        "sec": "RSA", // 密码算法RSA
        "type": "JWT" // 统一jwt类型
    };
    let payload = { // 定义数据信息, 可以自己定义, 但是不要放机密信息
        "nbf": new Date().getTime(), // 生效时间当前系统时间毫秒数
        "uid": uid, // 用户uid
        "lastTime": 60 * 1000 * tokenTimeout,
    }
    let sign = key.encryptPrivate(rsa.base64(header)+'.'+rsa.base64(payload),'base64')
    const token = rsa.base64(header)+'.'+rsa.base64(payload)+'.'+sign

    return token
}
exports.checkToken = async (token)=>{
    let arr = token.split('.');
    let key = rsa.nodersaG()
    let payload;
    if(arr.length===3 && arr[2]){
        const decryptbase = key.decryptPublic(arr[2], 'utf8')
        let headpay = decryptbase.split('.')
        if(arr[0]===headpay[0] && arr[1]===headpay[1]){
            payload = JSON.parse(rsa.base64decode(arr[1]))
            if(new Date().getTime() - payload['nbf'] < payload['lastTime']){
                return {code:true}
            }else{
                return {code:false,msg:'token过期'}
            }
        }else{
            return {code:false,msg:'token有误'}
        }
    }else{
        return {code:false,msg:'token有误'}
    }
}
// let token = getToken(33);
// console.log(checkToken(token))
  • config.js
exports.tokenTimeout = 60 //分钟
  • utils/result.js
//token有误,权限不够
exports.NOauthor = (msg = '权限非法') => {
    return {
        msg,
        code: 403,
        list
    }
}
  • route/index.js
// 登陆
router.get('/login',async (req,res,next)=>{
  let {name,password} = req['query']
  if(!name || !password){
    res.send(MError('账号信息不存在'))
    return
  }
  const result = await db.select(`SELECT * FROM user WHERE name = '${name}'`)
  if( result && password=== result[0].password){
    let token = await getToken(result.id)
    res.send(Success({...result,token}));
    return
  }else{
    res.send(MError('用户名或密码错误'))
    return
  }
})

二、vue中

1、登陆保存
userlogin(form.model).then(res => {
  if (res.code === 200) {
    ElMessage.success(res.msg);
    localStorage.setItem('token',res.list.token)//处理返回的token
    reset()
  } else {
    ElMessage.error(res.msg);
  }
})
2、请求拦截器
//请求拦截器 
axios.interceptors.request.use((config) => {
	showLoading()
	// token如果存在,统一在http请求的header都加上token
	const token = window.localStorage.getItem('token');
	token && (config.headers.Authorization = token)
	//若请求方式为post,则将data参数转为JSON字符串
	if (config.method === 'POST') {
		config.data = JSON.stringify(config.data);
	}
	return config;
}, (error) =>
// 对请求错误做些什么
Promise.reject(error));
3、响应拦截器
axios.interceptors.response.use((response) => {
	if(response.data && response.data.code === 403){
		localStorage.removeItem('token');
		ElMessage.error('请先进行登录之后再操作');
	}
    //响应成功
    return response.data;
}, (error) => {
    console.log(error)
    //响应错误
    //。。。。。。
});

可以将token时效设置短一些进行测试

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值