JS案例:接口加解密与防重放

news2024/12/24 8:36:04

目录

前言

功能设计

客户端的功能点(client)

服务端的功能点(server)

功能实现

工具函数

client.js(客户端)

server.js(服务端)

实现效果

写在最后


前言

在网络通信中,如果数据包是明文传输,并且包含敏感信息,那么就很容易被抓包窃取,因此加密手段也成了开发者耳熟能详的知识技能;常见的加密方法有对称加密和非对称加密。对称加密使用同一个密钥进行加密和解密,而非对称加密使用公钥和私钥分别进行加密和解密。

另一个需要知识点是防重放措施,防重放攻击是指攻击者会拦截请求并重新发送,从而导致重复处理。常见的防重放攻击方法有使用令牌桶算法和使用 Nonce 值(随机数)。

那么这二者为何会结合在一起呢?

原因是我之前做的零食商贩的案例暴露出来的问题,虽然接口做了加密处理,使用者不容易轻易知道数据包的内容,但是如果复制一个接口再次发起请求还是可以成功,因为接口没有做类似文件阅后即焚的功能,所以做个分享。

功能设计

因为客户端和服务端都在node中实现,所以通信暂时摒弃请求的方式,使用消息中心模拟前后端请求的操作

客户端的功能点(client)

  • 通过invoke发送请求
  • 创建Nonce随机值
  • crypto.aes加密参数

服务端的功能点(server)

  • 通过watch接收请求
  • Nonce查重
  • crypto.aes解密参数
  • 通过bcryptjs哈希处理对比密码是否正确
  • jsonwebtoken创建及校验token

功能实现

工具函数

helper.bcrypt.js(针对密码进行哈希盐加密)

const bcryptjs = require("bcryptjs");
// 哈希盐加密
exports.createBcrypt = (password, salt = bcryptjs.genSaltSync(10)) => {
  return bcryptjs.hashSync(password, salt);
};
// 校验密码
exports.checkBcrypt = (_password, _hash) => {
  return bcryptjs.compareSync(_password, _hash);
};

helper.random.js(生成随机数+时间戳的字符串)

const { randomNum } = require("utils-lib-js");
// 生成Nonce随机数
exports.createRandom = () => {
  const date = new Date().getTime();
  const start = 0x000000;
  const end = 0xffffff;
  return randomNum(start, end) + date;
};

helper.jwt.js(JSONwebtoken加解密)

const { sign, verify } = require("jsonwebtoken");
const { defer } = require("utils-lib-js");
const { TokenKey } = require("../config");
// 新建令牌
exports.createToken = ({
  payload = {},
  tokenKey = TokenKey,
  expiresIn = "1d",
  ...others
}) => {
  return sign({ payload }, tokenKey, {
    expiresIn,
    ...others,
  });
};
// 校验令牌
exports.checkToken = ({ token, tokenKey = TokenKey, options }) => {
  const { reject, resolve, promise } = defer();
  verify(token, tokenKey, options, (err, decoded) => {
    if (err) return reject(err);
    return resolve(decoded.payload);
  });
  return promise;
};

helper.crypto.js(使用AES对参数加解密)

const cryptoJS = require("crypto-js");
const { CryptoKey } = require("../config");
const { jsonToString, stringToJson } = require("utils-lib-js");
const defaultOpt = {
  mode: cryptoJS.mode.ECB,
  padding: cryptoJS.pad.Pkcs7,
};
const __key = CryptoKey; // 加密关键字
// 加密
const setCrypto = ({ data, key = __key, opts = defaultOpt }) => {
  return cryptoJS.AES.encrypt(jsonToString(data), key, opts);
};
// 解密
const getCrypto = ({
  str,
  key = __key,
  resToStr = true,
  opts = defaultOpt,
}) => {
  str = decodeURIComponent(str); //前端传参有特殊字符(中文)时转义(替换百分号)
  const bytes = cryptoJS.AES.decrypt(str, key, opts);
  const source = bytes.toString(cryptoJS.enc.Utf8);
  return resToStr ? stringToJson(source) : source;
};
module.exports = {
  setCrypto,
  getCrypto,
};

client.js(客户端)

const { createRandom } = require("./utils/helper.random");
const { setCrypto } = require("./utils/helper.crypto");
const { messageCenter } = require("event-message-center");
const { initServer } = require("./server");
const { catchAwait } = require("utils-lib-js");
let __token = null;
// 登录信息
const userInfo = {
  username: "zhangsan",
  password: "123123",
};
// 初始化服务端
initServer();
// 客户端加密混淆操作
const encryption = ({ query = {}, key = "params", token = __token }) => {
  query.id = createRandom(); //生成随机id混淆参数
  query.token = token;
  return {
    [key]: setCrypto({ data: query }).toString(),
  };
};
// 模拟前端用户登录操作
const userLogin = (query) => {
  const params = encryption({ query });
  return messageCenter.invoke("/login", params);
};
// 模拟登录成功后请求
const getInfo = (query) => {
  const params = encryption({ query });
  return messageCenter.invoke("/info", params);
};
// 初始化函数
const init = async () => {
  const [err, res] = await catchAwait(userLogin(userInfo));
  if (err) return console.error(err);
  __token = res.token;
  const [err2, info] = await catchAwait(getInfo());
  if (err2) return console.error(err2);
  console.log(info);
};
init();

server.js(服务端)

const { messageCenter } = require("event-message-center");
const { defer, catchAwait } = require("utils-lib-js");
const { getCrypto } = require("./utils/helper.crypto");
const { createBcrypt, checkBcrypt } = require("./utils/helper.bcrypt");
const { createToken, checkToken } = require("./utils/helper.jwt");
const __temp = new Map(); // 将请求过的id存起来(后续可以加定时任务清除缓存,或者增加长度限制)
const userInfo = {
  // 指代数据库取数据
  username: "zhangsan",
  password: createBcrypt("123123"),
};
// 解密操作
const decrypt = (query) => {
  return getCrypto({ str: query });
};
// 请求去重
const checkRepeat = (query = {}) => {
  const __id = query.id;
  if (!!!__id || __temp.has(__id)) return;
  return __temp.set(__id, query);
};
// 抛错
const promiseRej = (err) => Promise.reject(err);
// 加个简单的中间件,做校验
const middleware = {
  decrypt: (data) => {
    // 解密,重复请求校验
    const { resolve, reject, promise } = defer();
    const params = decrypt(data.params);
    if (!!!checkRepeat(params)) reject("重复请求或id为空");
    else resolve(params);
    return promise;
  },
  token: async ({ token, ...data }) => {
    // token校验
    const { resolve, reject, promise } = defer();
    const [err, username] = await catchAwait(checkToken({ token }));
    if (err) reject("token过期或失效");
    else resolve({ ...data, username });
    return promise;
  },
  checkPassword: async (data) => {
    // 密码校验
    const { resolve, reject, promise } = defer();
    if (!!!checkBcrypt(data.password, userInfo.password)) reject("密码错误");
    else resolve(data);
    return promise;
  },
  chackUser: async (data) => {
    // 用户校验
    const { resolve, reject, promise } = defer();
    if (data.username !== userInfo.username) reject("没找到用户");
    else resolve(data);
    return promise;
  },
};

exports.initServer = () => {
  messageCenter.watch("/login", async (data) => {
    const [err, params] = await catchAwait(middleware.decrypt(data));
    const [err2, params2] = await catchAwait(middleware.checkPassword(params));
    if (err || err2) return promiseRej(err ?? err2);
    return { token: createToken({ payload: params2.username }) };
  });
  messageCenter.watch("/info", async (data) => {
    const [err, params] = await catchAwait(middleware.decrypt(data));
    const [err2, params2] = await catchAwait(middleware.token(params));
    const [err3, params3] = await catchAwait(middleware.chackUser(params2));
    if (err || err2 || err3) return promiseRej(err ?? err2 ?? err3);
    return { msg: "获取成功", username: params3.username };
  });
};

实现效果

将init函数多执行几次,发现参数均不相同。

那么此时传递相同的参数会发生什么? 

可以看到,总共发送了五次请求,只有一次返回了结果,其余的全被中间件阻止

写在最后

感谢你耐心的看到了最后,如果文章对你有帮助还望多多支持,感谢!

源码:myCode: 基于js的一些小案例或者项目 - Gitee.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/157754.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

远程会计人员如何通过数字工作流程完成工作

远程会计人员如何通过数字工作流程完成工作 当大多数员工居家办公时,会计部门通常会敏锐地感受到挑战。如果您的组织开始数字化转型或只迈出了第一步,您会发文档管理系统现在已成为必备品,而不是可有可无的。现在是时候实施数字工作流程&…

论文投稿指南——中文核心期刊推荐(地理学)

【前言】 🚀 想发论文怎么办?手把手教你论文如何投稿!那么,首先要搞懂投稿目标——论文期刊 🎄 在期刊论文的分布中,存在一种普遍现象:即对于某一特定的学科或专业来说,少数期刊所含…

Mysql操作指令

Mysql操作指令 创建数据库 下面写具体案列一步步学 这里基本上线在sqlyog里面写sql,因为关于java程序和mysql数据库的连接还没有学 图形化操作数据库之前的可视化有 这里只讲指令凑在哦 注意 默认创建库 用uft8字符集,校对规则的uf8_general_ci不区分大…

< Linux >:环境变量

目录 环境变量 常见的环境变量 基本概念 查看环境变量内容的方法 测试环境变量PATH 与环境变量相关的命令 Linux操作系统下C/C程序代码中获取环境变量的方式 环境变量的组织方式 环境变量通常具有全局属性 环境变量 问题: 注意:可执行程序 等价于 命令/指…

Flutter 开发一个自己的 package(纯Dart)并提交到pub.dev上

小提示第一步:创建一个 package第二步:实现我们的package第三步:发布到 pub.dev 上。END发布的准备工作小提示 Flutter 轮子库。本文是为大家讲述怎么自己写个轮子发布到到上面。 https://pub.flutter-io.cn/packages 第一步:创建…

P2P僵尸网络-家族类别

Pink Pink 家族曾在中国境内感染了超过百万级的设备,其非实效性指令通过 P2P 传递,实效性强的指令通过集中控制的方式发布。是一个设计巧妙的 P2P 僵尸网络家族 Pink 僵尸网络概述 Pink 主要针对基于 mips 的光纤路由器,并且具有非常强大的和…

如何使用人力资源软件识别保留优秀员工

在企业信息化的时代,越来越多的年轻员工开始追求他们的激情,辞掉那些乏味的工作,而选择加入重视员工生活质量的企业。他们不再追随那些以牺牲员工福利为代价追求利润的公司。 员工认可度有助于加强组织中的团队合作关系,反过来&a…

VS 17.5 预览版2:/Gw链接开关的标准一致性改进

/Gw 链接开关可以告诉链接器优化代码中的全局数据,从而减小最终生成的二进制文件的大小。在 Visual Studio 17.5 预览版2中,我们新增了一个新的标志:/Zc:checkGwOdr[-] ,目的是在使用 /Gw 开关的时候改进对 C 标准的一致性支持。 …

表情包也能用 AI 生成?如何借助 AIGC 自定义专属表情包 #Memix

随着我们越来越离不开社交媒体,表情包也已经成为我们日常生活中的必备单品。有着个人鲜明风格的「表情包」,不仅是独特的「社交名片」,也能给人留下耳目一新的印象!谁的收藏夹里没有一堆私藏表情包呢!Memix借助 AI 技术…

画中画怎么制作?教你如何录制画中画视频,图文教学

很多小伙伴在录制视频的时候,都需要录制摄像头,以画中画的方式放置在视频当中。在网上眼花缭乱的录屏软件里,有不少的录屏软件是无法做到画中画视频的。画中画怎么制作?今天小编分享2个录制画中画视频的方法,一起来看看…

实心球状CdSe/ZnS/硫量子点QD-AFP-Ab/CPV VLP标记抗体/蛋白的制备方法与电镜表征

实心球状CdSe/ZnS/硫量子点QD-AFP-Ab/CPV VLP标记抗体/蛋白的制备方法与电镜表征 今天小编分享量子点标记蛋白,一起看看吧: 量子点标记蛋白的制备过程: 将纯化后的 CPV-VP2蛋白溶液(测定OD2802.0,蛋白浓度为2mg/ml)和…

CTK Plugin Framework插件框架学习--CTK服务工厂

一、前言 注册服务的时候能够用服务工厂来注册; 访问服务getServeice中的plugin参数是执行ctkPluginContext::getService(const ctkServiceReference&)的插件,从而工厂根据执行的不同插件名称返回不同的服务实现 服务工厂的作用 在服务中可以知道…

华为NAT实验配置

路由器基础配置 AR2 int g0/0/3 ip add 192.168.10.254 24 int g0/0/2 ip add 192.168.20.254 24 int g4/0/0 ip add 192.168.30.254 24 int g0/0/1 ip add 10.0.23.2 24 int g0/0/0 ip add 10.0.12.2 24 ip route-static 192.168.11.0 24 10.0.12.1 ip route-static 192.168.2…

2022年中国数据库排行榜年终盘点-墨天轮

深山虎啸雄风在,绿野兔奔好景来。 崭新的2023年已经到来,在2022年里,国产数据库行业发生了翻天覆地的变化,投融资此起彼伏,国产化替代进程加速,国产数据库行业发展的如火如荼。墨天轮12期中国数据库排行榜解…

Python验证中心极限定理

中心极限定理 中心极限定理提出了:无论总体服从什么分布,只要n充分大,那么样本均值分布就接近正态分布。 样本的数量越大,取样次数越多,样本平均值的分布也就越接近于一条正态分布曲线。普遍的经验是,样本…

redis 5种数据结构适用场景

网上介绍太笼统了,呕心沥血整理出来的可理解的适用场景,查看下图redis-cli指令大全:点击查看redis指令Redis支持5种数据类型:string(字符串)hash(哈希)list(列表&#xf…

飞行类手册国际标准简介

在业界有这个说法:三流的企业卖产品,二流的企业卖技术,一流的企业卖标准。一流企业是行业的标杆,行业的制定者。现在标准已经成为最重要的行业发展因素,谁的产品标准一旦为世界所认同,谁就会引领整个产业的…

指针进阶(C语言)

目录 字符指针 使用方法: 1、指向字符 2、指向字符串的首地址 指针数组 数组指针 数组指针的定义 数组名表示的含义 数组指针的使用 数组参数、指针参数 一位数组传参 二维数组传参 一级指针传参 二级指针传参 函数指针 函数指针数组 指向函数指针数组的指针 回调函…

网站让百度收录的技巧有哪些?网站在百度收录情况怎么查询

同样都是新上线的站点,为什么有的人是秒收,有的人可能要等很多时间才能收录,其实这些都是有技巧加快百度收录我们网站的。网站让百度收录的技巧有哪些?1、上线之前做好网站内容 我们在上线之前一定要做好充足的准备,这…

浏览器控制台接口学习

我们在做项目开发时,想知道一个功能调用了后台哪个接口,我们就可以在浏览器控制台中进行查看。 举一个例子,我们在看博客时,发现这一篇文章写的不错,我们在做点赞这个动作时,调用了哪个接口呢? …