目的
方案一:
aes、rsa配合加密方案:
- 对称加密生成密钥A(aes key)
- 用密钥A对数据进行对称加密,生成数据的密文a
- 使用服务器下发的非对称加密的公钥,对对称加密密钥A进行加密,生成密钥的密文b
- 和数据的密文a、生成密钥的密文b传递给服务端
aes对称加密,利用其加解密速度快的特点,对报文整体参数进行加解密;然后利用rsa的加密难破解的特点,对aes key进行加解密
方案二:
相对比较更安全的方案
公钥加密、私钥解密、私钥签名、公钥验签。
相关链接: https://www.cnblogs.com/pcheng/p/9629621.html
方案一具体实现
前端
1、aes加密采用Crypto.js插件
封装crypro.js
import { aesKey, aesIV } from './config'const CryptoJS = require('crypto-js'); //引用AES源码js// AES/CBC/PKCS7Padding 算法/模式/补码方式// 字符集 utf-8// mode 支持 CBC CFB CTR ECB OFB 默认CBC// padding 支持 Pkcs7 ZeroPadding NoPadding ... 默认 Pkcs7 即 Pkcs5// 前端 AES/CBC/Pkcs7 + iv// 后端 AES/CBC/Pkcs5 + ivconst key = CryptoJS.enc.Utf8.parse(aesKey); //十六位十六进制数作为密钥const iv = CryptoJS.enc.Utf8.parse(aesIV); //十六位十六进制数作为密钥偏移量//加密方法export const aesEncrypt = (data, k) => {const key = CryptoJS.enc.Utf8.parse(k || aesKey)let encrypted = CryptoJS.AES.encrypt(data, key, {iv: iv,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7,})// 转换为字符串return encrypted.toString()}//解密方法export const aesDecrypt = (data, k) => {const key = CryptoJS.enc.Utf8.parse(k || aesKey)let decrypted = CryptoJS.AES.decrypt(data, key, {iv: iv,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7,})// 转换为 utf8 字符串let decryptedStr = decrypted.toString(CryptoJS.enc.Utf8)return decryptedStr}
2、rsa采用jsencrypt.js插件
封装 jsencrypt.js
// 非对称加密 RSAimport JSEncrypt from "jsencrypt";import { rsaPubKey, rsaPriKey } from "./config";// 公钥加密export const rsaEncrypt = (data, key) => {const encryptor = new JSEncrypt() // 创建加密对象实例// 设置公钥encryptor.setPublicKey(key || rsaPubKey)// 加密const rsaCipher = encryptor.encrypt(data)return rsaCipher}// 私钥解密export const rsaDecrypt = (ciphertext, key) => {const decrypt = new JSEncrypt() // 创建解密对象实例// 设置私钥decrypt.setPrivateKey(key || rsaPriKey)const oriData = decrypt.decrypt(ciphertext)return oriData}
3、请求加解密封装,以及重发逻辑处理
/*** 封装axios* aes + rsa* 1、请求前body参数加密(不针对query参数)* VUE_APP_ENCRY '1'-加密 '0'-明文传输,便于查看参数* 2、获取响应数据,解密处理,判断 res.headers.keycipher 是否需要解密* 密文: 前端获取一律需要解密转 json*/import { Message } from 'element-ui';import axios from 'axios'import moment from "moment";import { isObject, isString, getItem } from './utils';import { aesKey, inUseMockdata } from "./config";import { aesEncrypt, aesDecrypt } from './crypto';import { rsaEncrypt, rsaDecrypt } from './jsencrypt';axios.defaults.headers.post["Content-Type"] = "application/json; charset=UTF-8"// 1. 创建新的axios实例,const instance = axios.create({baseURL: !inUseMockdata ? process.env.VUE_APP_BASEURL + process.env.VUE_APP_PREURL : '',// `withCredentials`指示是否跨站点访问控制请求withCredentials: true,// "responseType"表示服务器将响应的数据类型// 包括 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'responseType: 'json',// headers`是要发送的自定义 headersheaders: {// 'X-Requested-With': 'XMLHttpRequest','Cache-Control': 'no-store' // IE 禁用缓存 // 'no-cache'},// 超时时间 单位是ms,这里设置了10s的超时时间timeout: 10 * 1000,transformRequest: [function (data, headers) {// 这里没有对 Form-Data 格式的报文处理if (isObject(data)) {// 一、请求参数加密if (process.env.VUE_APP_RUNTIME === 'prod') {data = JSON.stringify(data)headers["keyCipher"] = rsaEncrypt(aesKey) // 传输 aes key 密文data = aesEncrypt(data) // 加密请求参数}return data}return data}],transformResponse: [function (data, headers) {if (isString(data)) {try {// 先对 axios 返回的源数据处理data = JSON.parse(data)/*** 二、获取响应数据之后解密* 判断 headers.keycipher 是否需要解密 (后端在接口报错的情况下,直接返回的是明文,不对错误信息加密)* 1、rsa 解密后端生成的 aes key* 2、aes 解密返参密文*/const { keycipher = '' } = headers || {}if (keycipher) {// 解密const resAesKey = rsaDecrypt(keycipher)const dataStr = aesDecrypt(data, resAesKey) || '{}'data = JSON.parse(dataStr)}console.log("res data ====", data);} catch (err) {console.log("transformResponse-err", err);}return data}}]})const codeMessage = {200: '服务器成功返回请求的数据。',201: '新建或修改数据成功。',202: '一个请求已经进入后台排队(异步任务)。',204: '删除数据成功。',400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',401: '用户没有权限(令牌、用户名、密码错误)。',403: '用户得到授权,但是访问是被禁止的。',404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',405: '请求方法未允许',406: '请求的格式不可得。',408: '请求超时',410: '请求的资源被永久删除,且不会再得到的。',413: '上传文件过大',422: '当创建一个对象时,发生一个验证错误。',500: '服务器发生错误,请检查服务器。',501: '网络未实现',502: '网关错误。',503: '服务不可用,服务器暂时过载或维护。',504: '网关超时。',505: 'http版本不支持该请求'};// 2. 添加请求拦截器instance.interceptors.request.use(config => {if (config.url) {config.headers = {...config.headers,timeStamp: new Date().getTime(), // 毫秒数token: getItem("authToken") || ""}// 必须为开发环境,api内的mock开关开启才生效if (process.env.NODE_ENV === "development" && inUseMockdata && config.mock && config.mockUrl) {// mock 生效路径config.url = config.mockUrl}// 请求路径增加时间戳,防止命中缓存config.url += `?timeStamp=${config.headers.timeStamp}`return config;} else {return Promise.reject('接口不合法');}}, error => {// 对请求错误做些什么console.log('request-err', error);Message.error(error);return Promise.reject(error);});let retry = 2 // 重发次数let retryDelay = 200 // 重发时延// 3. 添加响应拦截器instance.interceptors.response.use(response => {// 对响应数据做点什么return response.data;}, error => {// 对响应错误做点什么console.log('response-err', error);/****** 接收到异常响应的处理开始* 跨域存在获取不到状态码的情况* *****/if (error && error.response) {// 1.公共错误处理// 2.根据响应码具体处理const { status, config, statusText } = error.response;if (status) {const errorText = codeMessage[status] || statusText || '出错了';Message.error(`请求错误 ${status}: ${config.url}\n${errorText}`);} else {Message.error('您的网络发生异常,无法连接服务器');}} else {// 其它异常处理console.log("error!: " + JSON.stringify(error));// 重发逻辑let { config = {} } = error || {}// 记录单个api的请求次数config.requestCount = config.requestCount || 1if (config.requestCount < retry) {config.requestCount++ // 重发次数累加let backoff = new Promise((resolve) => {setTimeout(() => {resolve()}, retryDelay || 100);})// 重发 更新时间戳const retryUrl = config.url.split("?")[0] + `?timeStamp=${new Date().getTime()}`config.url = retryUrlconsole.log("retry config ===", config);return backoff.then(() => {return axios.request(config)})} else {Message({message: "连接服务器失败,请稍后再试",type: "error"})}/* 超时处理无法通过判断是否存在 timeout 字符,来确定服务是否连接超时因为 error 信息里包含请求的 config 信息,里面配置的 timeout 字段 */if (JSON.stringify(error).toLocaleLowerCase().includes('timeout')) {Message.error('服务器响应超时,请刷新当前页');} else {Message({message: '连接服务器失败',type: "error"})}}/***** 处理结束 *****/return Promise.reject(error);});//4.导出文件export default instance
