nodejs学习
- 1、express & nodejs & mysql 新建项目
- 2、nodejs尝试登陆接口
- 3、nodejs的前端项目搭建以及登陆接口开发
- 4、前端上传图片formdata格式,后端接口处理
- 5、vue+nodejs双表联动
- 6、nodejs插件knex & 日志打印
- 7、vue3+nodejs基于RSA加密的身份认证
- 前端代码地址(不完全功能)
- 后端代码地址(不完全功能)
Token = header.payload.signature
= base64(header).base64(payload).RSA(base64(header).base64(payload))
组成:令牌的类型(即JWT)和所使用的签名算法,签名算法例如HMAC SHA256或RSA。
let header = { // token标头信息
"alg": "RSA", // 签名算法RSA
"typ": "JWT" // 统一jwt令牌类型
};
有关实体(通常是用户)和其他数据的声明。
let payload = { // token有效负载,可自定义
"nbf": new Date().getTime(), // 生效时间当前系统时间毫秒数
"uid": uid, // 用户uid
"lastTime": 60 * 1000 * tokenTimeout,//有限时间
}
header和payload都是Base64编码过的,中间⽤
.
隔开,Signature就是使用加密算法将header和payload合起来做签名,RSA(base64(header).base64(payload))
,签名的作用是保证 JWT 没有被篡改过
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)
工具类
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)
}
}
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))
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))
全局拦截器
要放在 声明路由的上面 才会生效
//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
}
})
//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));
}
}
}
});
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))
exports.tokenTimeout = 60 //分钟
//token有误,权限不够
exports.NOauthor = (msg = '权限非法') => {
return {
msg,
code: 403,
list
}
}
// 登陆
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
}
})
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);
}
})
//请求拦截器
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));
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时效设置短一些进行测试