导入插件
Vuex的modules模块里,初始化AES密匙,存做全局变量,例如我创建了 /store/modules/app.js 模块(对Vuex不甚明白的,看Vuex官方文档)
import { initAesKey, rsaEncrypt } from '@/js_sdk/encryption/utils';
const state = {
publicKey: null, // RSA加密公匙
aesKey: null, // AES加密密匙
aesEncryptKey: null, // AES加密密匙的RSA加密字符串
}
const mutations = {
SET_PUBLICKEY: (state, publicKey) => {
state.publicKey = publicKey
},
SET_AESKEY: (state, aesKey) => {
state.aesKey = aesKey
},
SET_AESENCRYPTKEY: (state, aesEncryptKey) => {
state.aesEncryptKey = aesEncryptKey
},
}
const actions = {
// 初始化请求,获取RSA公匙
async initPublicKey({
commit
}) {
// 这里的AppApi是我事先封装好的网络请求,用来向服务器获取公匙的,你要替换成你的网络请求
const res = await AppApi.getPublicKey()
if (res.code) {
// 存下RSA公匙
commit('SET_PUBLICKEY', res.data.key)
// 随机生成AES密匙,并存下来
commit('SET_AESKEY', initAesKey())
// 将AES密匙进行RSA加密,并存下来
commit('SET_AESENCRYPTKEY', rsaEncrypt(state.aesKey, state.publicKey))
}
return res;
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
在main.js中调用app.js里的initPublicKey方法
// 初始化通信公匙
store.dispatch('app/initPublicKey').then((res) => {
// 获取公匙完成,开始加载网页
app.$mount()
})
完成2,3两步,现在你的APP在启动之时,即初始化了publicKey,aesKey,aesEncryptKey三个变量,接着我们来到http拦截器(如果你没有用到http拦截器,强烈建议你去插件市场搜索一款)
这里是我的http拦截器代码
import { aesEncrypt, aesDecrypt } from '@/js_sdk/encryption/utils';
/**
* 全局配置
* 只能配置 静态数据
* `content-type` 默认为 application/json
* `header` 中`content-type`设置特殊参数 或 配置其他会导致触发 跨域 问题,出现跨域会直接进入响应拦截器的catch函数中
*/
export const config = {
header: {
// 这里要注意,请求内容设置为 "text/plain",不要是其他的例如键值对类型
contentType: "text/plain"
}
}
/**
* 全局 请求拦截器, 支持添加多个拦截器
* 例如: 配置token、添加一些默认的参数
*
* `return config` 继续发送请求
* `return false` 会停止发送请求,不会进入错误数据拦截,也不会进入请求对象中的catch函数中
* `return Promise.reject('xxxxx')` 停止发送请求, 会错误数据拦截,也会进入catch函数中
*
* @param {Object} config 发送请求的配置数据
*/
globalInterceptor.request.use(
config => {
if(Vue.prototype.$store.getters.aesEncryptKey) {
// 将AES密匙(RSA加密后)放入请求头中
config['header']['aes-key'] = Vue.prototype.$store.getters.aesEncryptKey;
if(!config['data']) {
config['data'] = {};
}
// 将时间戳放入请求内容里面
config['data']['timetoken'] = Vue.prototype.$store.getters.timestamp;
// 将请求内容字符串化,进行AES加密
let data = aesEncrypt(JSON.stringify(config['data']), Vue.prototype.$store.getters.aesKey);
// 将请求内容替换成加密后的字符串,如果你的请求内容是键值对类型,这里会出错的
config['data'] = data;
}
return config;
},
err => {
console.error("is global fail request interceptor: ", err);
return false;
}
);
/**
* 全局 响应拦截器, 支持添加多个拦截器
* 例如: 根据状态码选择性拦截、过滤转换数据
*
* `return res` 继续返回数据
* `return false` 停止返回数据,不会进入错误数据拦截,也不会进入catch函数中
* `return Promise.reject('xxxxx')` 返回错误信息, 会错误数据拦截,也会进入catch函数中
*
* @param {Object} res 请求返回的数据
* @param {Object} config 发送请求的配置数据
* @return {Object|Boolean|Promise}
*/
globalInterceptor.response.use(
(res, config) => {
if (res.statusCode == 200) {
const code = parseInt(res.data.code);
if(code) {
// 如果有code,说明返回的是明文,这种请求也就是在初始化获取RSA公匙的那一次才会有
return res.data;
}else{
// AES解密
let content = aesDecrypt(res.data, Vue.prototype.$store.getters.aesKey);
// 明文json化,返回
return JSON.parse(content)
}
} else {
uni.showModal({
title: "请求错误",
showCancel: false,
content: "errMsg:" + res.errMsg + "\nstatusCode:" + res.statusCode
})
return Promise.reject(res, config);
}
},
(err, config) => {
// 请求错误, 有可能服务器没开, 有可能是跨域问题, 情况非常多, 不可能一一进行逻辑处理, 只能弹窗提示
uni.showModal({
title: "请求异常",
content: err.errMsg,
showCancel: false
})
return Promise.reject(err);
}
);
以上就是js端的代码,仅作参考,请根据自己代码进行调整
后端代码,这里暂时只提供php代码作为参考,以下是thinkphp6.0框架的模块中间件代码
namespace app\api\middleware;
use app\common\model\db\WebConfig;
use app\Request;
class Encryption
{
/**
* 处理请求
* @param $request
* @param \Closure $next
* @return mixed|\think\response\Json
*/
public function handle(Request $request, \Closure $next)
{
$aesKey = $request->getAesKey();
if ($aesKey && $aesKey != 'null') {
// 获取RSA私匙
$privateKey = WebConfig::getRSAPrivateKey();
// $encrypted为需要解密的数据(如果加密的时候用了base64,这里则需要解码),$decrypted为解密后的数据,$privateKey同上,为私钥密钥
openssl_private_decrypt(base64_decode($aesKey), $decrypted, $privateKey);
// 解密之后的字符串 $decrypted ,为aes密匙
$request->aesKey = $decrypted;
$content = $request->getContent();
// aes解密
$_content = aesDecrypt($content, $decrypted);
// 转json
$contentArr = json_decode(trim($_content), true);
if ($contentArr) {
foreach ($contentArr as $k => $v) {
$request->$k = $v;
}
}
if ($request->timetoken) {
// 这里的时间,请自己根据实际情况做调整,毕竟timetoken这是客户端时间,客户端时间很有可能不是准的,我是在客户端弄了socket连接通过心跳来校准时间
if (time() - $request->timetoken > 10 || time() - $request->timetoken < -5) {
// 时间异常
return json(config(TimeException));
}
} else {
return json(config(TimeException));
}
return $next($request);
} else {
// 没有AES密匙,要么非法,要么当前是 getPublicKey 方法
$pathinfo = $request->pathinfo();
$urls = explode('/', $pathinfo);
$controller = $urls[0];
$action = $urls[1];
if ($controller == 'web' && $action == 'getPublicKey') {
// 如果当前是在行获取公匙的路由,那么可以放行
return $next($request);
} else {
return json(config(AESNo));
}
}
}
}
php的RES加密解密方法如下
/**
* AES解密
* @param $content
* @param $key
* @return false|string
*/
function aesDecrypt($content, $key)
{
return openssl_decrypt($content, 'AES-256-CBC', $key, OPENSSL_ZERO_PADDING, 'ZZWBKJ_ZHIHUAWEI');
}
/**
* AES加密
* @param $content
* @param $key
* @return false|string
*/
function aesEncrypt($content, $key)
{
return openssl_encrypt($content, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, 'ZZWBKJ_ZHIHUAWEI');
}
thinkphp6的统一返回管理
/**
* 返回给前端数据标准化接口
* @param $data
* @param $msg
* @param $code
* @param $key
* @return false|string|\think\response\Json
*/
function apidata($data, $msg, $code, $key)
{
$content = [
"data" => $data,
"msg" => $msg,
"code" => $code,
];
if ($key) {
return base64_encode(aesEncrypt(json_encode($content), $key));
} else {
return json($content);
}
}