JS前端接口请求参数混淆方案分享
作者:木亦Sam 发布时间:2024-04-18 10:53:17
写在前面
在一些接口请求的场景中,我们希望携带的数据不希望是以明文的方式提交的,也就是需要对参数做一些混淆或者加密处理,后端拿到数据后再进行解密,得到真实数据。
其目的是为了保护数据的安全,以及提高被识破成明文的门槛。在例如用户登录的接口请求中,如果账号和密码是以明文传输的,会容易导致一些安全性问题,同时,我们也不希望谁都可以伪造参数对接口发起请求,因此前端参数混淆非常有必要,这里分享一个自用的方案,其目的在于:
防止信息泄露
防止参数被随意篡改
提高应用安全性和稳定性
对于加密或者混淆的处理方式的选择,例如base64
、MD5
等安全性不高或者不可逆的算法,不适用于我们这个场景,而是可以利用Aes
和Rsa
两者结合的方式,实现数据的加解密。
什么接口的参数需要做处理
从安全角度出发,这里一致认为,只要是对数据库有新增、修改、删除操作的,一律需要做混淆/加密,这一类接口,多为post
、put
、delete
等请求。
而对于get
请求,如果是规范化的接口,一般只是获取数据,不会对数据库有直接的操作,故不需要做参数处理。
当然还有post
请求和一些文件上传等,不希望做参数处理的接口,需要特殊处理,以及在开发环境中,为了方便调试,也不需要做处理。
参数处理
因为Rsa
在处理数据量较大时优势不明显,适合处理数据量较小的场景,所以对参数数据的处理采用了Aes
加密,而参与加密的密钥key
则采用Rsa
非对称加密提高破解难度。
涉及到的相关依赖及其版本号:
"crypto-js": "^4.1.1",
"jsencrypt": "^3.2.1"
这里使用的方案是,先将原始数据处理为query
形式的字符串,然后将其使用随机字符串作为密钥,参与Aes
加密,并截取特定的字符串,作为原始密钥,再进行一次Aes
加密,最后将原始密钥使用与后端约定好的公钥进行Rsa
加密处理,具体流程和算法如下:
对参数排序、提取
query
字符串处理将提取的字符串利用随机字符串加密并截取,得到密钥
利用密钥对原始参数进行
Aes
加密将密钥进行
Rsa
非对称加密输出最终的
data
和key
/**
* 加密请求数据
* @param {Object} rawData 原始数据
* @returns {data, key}
*/
export function encryptRequestData(rawData) {
// 字典排序并赋值
var result = {}, str = [], arr = Object.keys(rawData).sort();
arr.map(m => { result[m] = rawData[m] });
// 处理成 query 形式字符串
for (var p in result)
result.hasOwnProperty(p) && str.push(encodeURIComponent(p) + "=" + encodeURIComponent(result[p]));
result = str.join("&");
// 参与 Aes 加密的密钥,将处理后的字符串用 16 位随机码对称加密,再从第 3 位开始获取 16 位原始密钥
const rawKey = aesEncrypt(result, randomString(16)).substr(3, 16);
// 输出最后的加密参数
const data = aesEncrypt(JSON.stringify(rawData), rawKey);
const key = rsaEncrypt(rawKey);
return { data, key }
}
Aes加密
/**
* Aes 加密
* @param {String} data
* @param {String} customer_key
* @returns encrypted
*/
export function aesEncrypt(data, customer_key = "") {
var key = CryptoJS.enc.Utf8.parse(customer_key);
var messageHex = CryptoJS.enc.Utf8.parse(data);
var encrypted = CryptoJS.AES.encrypt(messageHex, key, {
"mode": CryptoJS.mode.ECB,
"padding": CryptoJS.pad.Pkcs7
});
return encrypted.toString();
}
Rsa加密
/**
* Rsa 加密
*/
export function rsaEncrypt(rawData) {
let data;
try {
var rsa = new JsEncrypt();
rsa.setPublicKey(publicPem);
data = rsa.encrypt(rawData)
} catch (error) {
return null
}
return data;
}
签名验证
如果需要进一步加强防篡改,可以在处理参数的时候,通过一定算法得出一个签名sign
值,一并提交到后端,后端解密后,也通过同样的算法将解密后的数据生成一个sign
值,与提交的作对比,判断是否为合法的请求来源。 这里不做详细介绍。
处理时机
我们需要在请求拦截的时候对参数做混淆处理,这里只针对post
请求以及非本地环境,另外为了方便调试,除了开发环境外 在实例上还增加了uncrypt
来标识哪些接口可以不参与处理。
// 请求拦截
instance.interceptors.request.use(
config => {
config.headers.contentType = "application/x-www-form-urlencoded"
const token = local.get('token')
token && (config.headers.Authorization = token)
const { method, uncrypt = false, data = {} } = config;
(method === 'post' && !uncrypt && cfg.NODE_ENV === 'development') && (config.data = encryptRequestData(data));
return config
},
error => Promise.error(error)
)
后端实现
前端参数处理后以data
和key
组成的对象提交至后端,服务层接受后进行解密,这里以 Egg.js 的实现为例子。 涉及依赖及其版本号:
"crypto-js": "^4.1.1",
"node-rsa": "^1.1.1"
Rsa解密
// RSA 解密
rsaDecrypt(data) {
let dataObj;
return new Promise(function (resolve, reject) {
// 私钥存放在app/extend/pem/private.pem
fs.readFile('app/extend/pem/private.pem', function (err, pem) {
const key = new NodeRSA(pem, 'pkcs8-private');
key.setOptions({ encryptionScheme: 'pkcs1' });
try {
dataObj = key.decrypt(data, 'utf8');
} catch (e) {
const second = new NodeRSA(pem, 'pkcs8-private');
try {
dataObj = second.decrypt(data, 'utf8');
} catch (error) {
reject("Rsa 解密失败");
}
}
resolve(dataObj);
});
});
}
Aes解密
// Aes 解密
aesDecrypt(data, customer_key = "") {
var key = CryptoJS.enc.Utf8.parse(customer_key || this.config.secret.aes.key);
var decrypt = CryptoJS.AES.decrypt(data, key, {
"mode": CryptoJS.mode.ECB,
"padding": CryptoJS.pad.Pkcs7
});
return CryptoJS.enc.Utf8.stringify(decrypt);
}
处理中间件
新建解密处理的中间件app/middleware/security.js
module.exports = () => {
return async function (ctx, next) {
const { helper, request } = ctx;
const { ajaxMsg } = helper;
const { key, data } = request.body;
if (!key || !data) return ajaxMsg(ctx, "-1", "请求参数错误", null, 400);
let rawKey;
try {
rawKey = await helper.rsaDecrypt(key);
} catch (error) {
return ajaxMsg(ctx, "-1", "密钥解析失败", null, 400)
}
if (!rawKey) return ajaxMsg(ctx, "-1", "密钥解析失败", null, 400);
const decryptData = JSON.parse(helper.aesDecrypt(data, rawKey));
if (!decryptData) return ajaxMsg(ctx, "-1", "安全验证未通过", null, 400);
request.body = decryptData;
await next();
};
};
路由中使用
const { router, controller, middleware } = app;
const security = middleware.security(); // 接口参数加密
router.post('/common/user/login', security, controller.common.user.login); // 登录
来源:https://juejin.cn/post/7124108207005368356


猜你喜欢
- 1.变量的赋值操作只是多生成了一个变量,实际上还是指向同一个对象# -*- coding: utf-8 -*-class CPU: &nbs
- 马上就是圣诞节了,先提前祝大家圣诞快乐!:christmas_tree::christmas_tree::christmas_t
- 这个例子可作为一个模式,在你需要的时候套用。<!DOCTYPE HTML PUBLIC &q
- 先写一个SQLSELECT DISTINCT from_idFROM codWHERE cod.from_id NOT IN (37, 56
- 程序的功能有了个大体的框架,其实可以自己添加一些功能,比如开始的数据库连接 ,可以先设置变量然后通过INIT() 来选择
- 前言如果你的 Python 程序程序有大量的 import,而且启动非常慢,那么你应该尝试懒导入,本文分享一种实现惰性导入的一种方法。虽然P
- 博主做过比较多项目的archive脚本编写,对于这种删除数据的脚本开发,肯定是一开始的话用最简单的一个delete语句,然后由于部分表数据量
- 作为站长而言,有时候我们可能会碰到更换服务器或者为网站做一个整体布局修改和升级,我们首先需要做的就是对数据库和整个wordpress网站的
- Python命名空间和作用域总结emmm,这一块讲了2个内容,一个是命名空间,一个是作用域。一个一个说吧命名空间A namespace is
- 很久之前用到的,现在整理在这,里面一些代码来源于网上,不过有些bug已被我修改了。1.查询结果转XMLDECLARE @ParameterS
- 软件版本:apache:Apache 2.4.6 Win64 PHP:PHP 5.5 VC11 x64 Non Thr
- 首先介绍一下什么是桑葚图?桑基图(Sankey diagram),即桑基能量分流图,也叫桑基能量平衡图。它是一种特定类型的流程图,图中延伸的
- 1、Git本地版本库结构如下图所示:工作区(Working Directory)添加、编辑、修改、删除文件等操作。暂存区(Stage)打算提
- 字典的本质就是 hash 表,hash 表就是通过 key 找到其 value ,平均情况下你只需要花费 O(1) 的时间复杂度即可以完成对
- 基本使用import unittestclass Testcase(unittest.TestCase): @classmeth
- 问:怎样才能取得局域网中所有SQL Server的实例?答:请参考以下的具体步骤:SmoApplication.EnumAvailableS
- 本文针对MySQL数据库基本操作进行学习研究,需要了解的朋友不要错过这篇文章。以下均是在Windows 64位操作系统下的命令行使用。学习之
- 游标游标(Cursor)是用于查看或者处理结果集中的数据的一种方法。游标提供了在结果集中一次一行或者多行前进或向后浏览数据的能力。游标的使用
- 一、Position1、语法:position:static/ absolute / fixed / relative2、参数:(1)sta
- py2exe在sourceforge 的下载只支持到2.7。针对python3.0+的版本,需要自己编译。1.下载源码svn checkou