Nodejs前后端数据加密传输二种方案

项目场景:

在前后端分离,前端使用Axios 或者是Ajax 来发送数据和接收数据,对应非程序人来说就没有有,应为他们也不懂,对应懂程序地来说 直接F12 查看浏览器地请求(Network)来查看请求数据,和返回数据,对应我们本身开发者来说无疑是在当中众多同行种裸奔


问题描述:

为了防止数据传输过程中裸奔 进行数据加密传输

技术栈(ES6+语法)
  1. NodeJS后端 Typescript Express
  2. 前端 uni-app
  3. 加密方法 使用原生JS数据加密(前后端统一方法进行转码解码)TS兼容JS语法

原因分析:什么时候进行加密:

  • 前端

前端发送数据地时候 进行数据加密,
每一个发送数据都要加密?
这个看自己怎么对业务请求代码地封装了(建议二次封装请求,方便传输时候进行统一地加密)
什么时候对返回数据地解码?
(在二次封装请求数据)在数据回来地时候对数据解码能够统一,全局也只写一次,


解决方案(一):

加密数据技术点(前端方案)
  1. atob
  2. btoa,
  3. encodeURIComponent
  4. decodeURIComponent
    前面文章有讲解这几个知识点以及实际地应用

前端加密核心代码 使用ES6语法创建类,加密和解密类 (前后端采用同方法和函数)

class unscrambler {
	constructor(setDecode, getDecode) {
		Object.assign(this, {
			setDecode,
			getDecode
		});
	}
}

// 设置
const setDecode = setObj => {
	if (Object.keys(setObj).length > 0) setObj = JSON.stringify(setObj);
	let set = encodeURIComponent(setObj),
		result = btoa(set);
	return result;
}

// 获取解密
const getDecode = getData => {
	let jieMi = atob(getData),
		jieM = decodeURIComponent(jieMi);
	try {
		return JSON.parse(jieM)
	} catch (e) {
		return false
	}
}

let decode = new unscrambler(setDecode, getDecode);

前端 uniapp 请求封装

注意点

  • 由于加密有数据不是对象所有不能够使用 content-type = application/json 和其他地 只能使用 text/xml 或者是 application/xml 的content-type
  • 在自定义header 头部的KEY 的时候必须要同后端一同设置,否则或出现异常
  • 在数据回来的时候 对数据进行解密在返回出去
export default class RequestHttp {

	static req(Interface, data, method = "POST", dataType = "json") {
			// ==========数据的加密发送 S
		let sendData = data && decode.setDecode(data);
			// ==========数据的加密发送 E
		let url = `${getState("api", "baseUrl")}${Interface}`;
		let Authorization;
		uni.getStorage({
			key: 'userInfo',
			success: (res) => {
				Authorization = res.data.token
			}
		});

		return new Promise((resolve, reject) => {
			uni.request({
				url,
				data:sendData,
				header: {
					SelfSummerHeader: "hello", //自定义请求头信息
					"content-type": 'application/xml',
					Authorization
				},
				method,
				dataType,
				fail() {
					showMessage("网络请求失败");
				},
				success(response) {
					!response.data && showMessage("接口报错,报文无data数据");
					if (response &&[200, 201, 204,304].includes(response.statusCode)) {	
						//=========================
						//进行数据的解密
						const receiveData = decode.getDecode(response.data);
						//=========================
						switch (receiveData.StatusCode) {
							// 当请求返回是的状态码正确是才返回数据
							case 200:
								let data = receiveData.data;
								resolve(data);
							default:
								showMessage('请求失败');
								reject(response);
						}
					} else {
						showMessage("网络异常");
					}
				},
			});
		});
	}
}

Nodejs后端解决方法

  • 后端

    在什么时候进行传递过来地数据解码?
    建议在接收到数据地时候解码
    什么时候加密返回
    发送数据就加密返回

    在每一次API接口前面来写解码解码太麻烦,还不够统一,所有我们自定义中间件来接收数据
    NodeJS自定义中间件,前面有文章讲解 NodeJS+Express 自定义中间件

  • 接收数据在自定义中间件

  • 前端传入是加密后的字符串,所有不能使用body-parser 插件(只能够自己写)

  • 涉及到的文件上传使用了 multer 插件 下面定义中间件的时候要注意,否则会引发后面API接口不响应

  • 接收收据要在 req.on 注册事件来接收 并且解密

  • 把数据接收后再把数据直接挂载到req 上面,后续API接口直接可以在 req.bodyData 上面 就有传输的数据了

  • 使用 Express 搭建Node后台服务

  • 路由挂载

  • 中间件挂载

  • 设置跨域

  • 导出配置到 server.ts 作为启动文件

var express = require("express"); //Express框架
var router = express.Router(); //Express 路由
const { systemOs, findSync,portIsOccupied } = require("../PunlicFunc/PublicFunction"); //公共封装的方法
var app = express();
var fs = require("fs");
const COMPort: number = 8978; //端口号

var join = require("path").join;

// 自定义Nodejs中间件
const middleware_router = require("../middleware/middleware_router");

app.listen(COMPort, (err: Error) => {
  if (err) {
    console.log('启动错误', err)
  }else{
 console.log('服务启动成功', COMPort)
  // 挂载自定义中间件
  app.use(middleware_router.bodyData,middleware_router.unifiedErr);
}
});

app.use(express.json()); //吧客戶端传递的参数解析为json  会导致Node服务接受不到请求
app.use(express.urlencoded({ extended: true }));


//设置跨域访问
app.all("*", (req: any, res: any, next: Function) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header(
    "Access-Control-Allow-Headers",
    "Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild"
  );
  res.header(
    "Access-Control-Allow-Headers",
    "content-type,SelfSummerHeader,Authorization"
  ); //自定义请求头
  res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
  res.header("X-Powered-By", "3.2.1");
  res.header("Content-Type", "application/json;charset=UTF-8");
  res.header("Content-Type", "text/xml");

  if (req.method === "OPTIONS" || req.url === "/favicon.ico") {
    // 拦截请求
    return res.send();
  }
  next();
});

// 路由挂载
 app.use("/Mailbox", require("../../ServerApi/ExtendedServices/api.ts"));

//访问根目录需要干事

app.get("/", (res: any, req: any) => {
  req.send("路径非法");
});

app.post("/", (res: any, req: any) => {
  req.send("路径非法");
});

// 中间件集合
const middleware = { ...middleware_router };

/**
 * 向外暴露 Express配置好的 框架主方法
 */
export {
  app,
  express,
  router,
  middleware,
};

  • 自定义数据加密方法

  • Nodejs v16 版本后才支持 atob, btoa ,之前版本是不支持的使用 Buffer.from()函数代替

  • 新建PublicFunction.ts用于公共方法

class unscrambler {
  constructor(setDecode: Function, getDecode: Function) {
    Object.assign(this, { setDecode, getDecode });
  }
}
// 设置加密参数
const setDecode = (setObj: string) => {
  try {
    if (Object.keys(setObj).length > 0) setObj = JSON.stringify(setObj);
    let set = encodeURIComponent(setObj), result = Buffer.from(set, 'binary').toString('base64');
    return result;
  } catch (error) {
    return null
  }
}

//解密参数
const getDecode = (getData: string) => {
  let jieMi = Buffer.from(getData, 'base64').toString('binary'),
    jieM = decodeURIComponent(jieMi);
  try {
    return JSON.parse(jieM)
  } catch (e) {
    return null
  }
}

let decode = new unscrambler(setDecode, getDecode);


/**
 * 获取目录下面所有的文件名称以及路径
 * @param startPath  String
 * 返回指定目录下面的文件路径
 */

function findSync(startPath: String): Array<Object> {
  let result: Array<Object> = [];

  let finder = (path: String) => {

    let files = fs.readdirSync(path);

    files.forEach((val: string, index: number, list: Array<string>) => {

      let fPath = join(path, val), stats = fs.statSync(fPath);

      if (stats.isDirectory()) finder(fPath);

      if (stats.isFile()) {
        let add = fPath?.replace(/\\/g, "/");
        result.push({
          FileUrl: add,
          paths: add.split("/"),
          FileName: val,
          type: new mailboxSuffix().int(val, "."),
        });
      }

    });

  }
  finder(startPath);
  return result;
}
export { findSync,decode };

定义 bodyData 中间件 (新建/middleware/middleware_router.ts)

const { decode  } = require("../PunlicFunc/PublicFunction"); //公共封装的方法
const bodyData = (req: { body: object, query: object, bodyData: object, on: Function, decode: object, files: Array<Object> }, res: any, next: Function) => {
  req.decode = decode;

  res.sendSuccess = (data: any, StatusCode?: string, msg: String = '成功') => {

    if (Object.keys(data).length === 3) res.send(decode.setDecode({ ...data }))

    try {
      let news = null, code = null;
      if (isType(StatusCode) === String) {

        news = StatusCode;
        code = 200;

      } else {
        news = msg;
        code = StatusCode || 200;
      }

      res.send(decode.setDecode({ StatusCode: code || StatusCode, data, msg: news || msg }));
    } catch (error) {

    }

  }

  res.sendFail = (StatusCode: number = 400, msg: String, err: Error) => res.send(decode.setDecode({ StatusCode, msg, err }));

  let str = '';
  req.on("data", (dt: string) => {
    str += dt
  })
  req.on("end", () => {

    let JSONS = decode.getDecode(str);

    req && (req.bodyData = { ...JSONS });

    next instanceof Function && next();
  })

  if (Array.isArray(req.files)) next instanceof Function && next();
};
  • 发送数据也在同一个中间件里面进行加密房啊的挂载,后面每一个路由里面用自定义的函数进行返回数据,并且返回的数据是加密了的

  • 新建api.ts

router.all("/login", async (req: { query: Object, bodyData: { uerMail: string; uerPwd: string }, decode: any }, res: any,next:Function) => {
  const { uerMail, uerPwd } = req.bodyData;
  let lookup = await db('SELECT us.uerid , us.usermali FROM `userinfo` as us WHERE usermali = "' + uerMail + '"');

  if (Array.isArray(lookup) && lookup.length === 0) return res.sendFail(240, lookup)
  let data = await db('SELECT us.uerid , us.usermali,us.nickname,us.msg,us.img FROM `userinfo` as us WHERE usermali = "' + uerMail + '"and userpwd ="' + uerPwd + '"')
  if (data.length === 1) {
    const { uerid, usermali } = data[0];
    let token = new toke_Jwt().generateToken({
      uerid,
      usermali,
    });
    return res.sendSuccess({ ...data[0], token }, "登录成功");
  } else {
    return res.sendFail(260);
  }
}
);

看下效果 前端效果
Nodejs前后端数据加密传输二种方案_第1张图片

可以在req 上面看到我们自定的req.bodyData对象,里面是解密后前端传递过来的参数
Nodejs前后端数据加密传输二种方案_第2张图片

解决方法(二)

使用 crypto-js


npm i crypto-js

crypto-js 可以在任何js环境使用,包含Node.js 都可以使用

注意如果在小程序环境使用,记得使用 4 以下的版本否则会出错

npm i [email protected]

数据加密

import CryptoJS  from 'crypto-js'
 data = CryptoJS.AES.encrypt(JSON.stringify(data), '自定义加密字符出key').toString();
 consloe.log(data)

数据解密

	import CryptoJS  from 'crypto-js'
let	bytes  = CryptoJS.AES.decrypt(getData, '自定义加密字符出key'),
	originalText = bytes.toString(CryptoJS.enc.Utf8);
	console.log(originalText)

更多使用教程 crypto-js

你可能感兴趣的:(javascript,uniapp,Nodejs,typescript,node.js,vue.js)