目的

确保前后端传输数据的安全性,避免明文显示,避免数据被篡改

方案一:

aes、rsa配合加密方案:

  1. 对称加密生成密钥A(aes key)
  2. 用密钥A对数据进行对称加密,生成数据的密文a
  3. 使用服务器下发的非对称加密的公钥,对对称加密密钥A进行加密,生成密钥的密文b
  4. 和数据的密文a、生成密钥的密文b传递给服务端

aes对称加密,利用其加解密速度快的特点,对报文整体参数进行加解密;然后利用rsa的加密难破解的特点,对aes key进行加解密

方案二:

相对比较更安全的方案
公钥加密、私钥解密、私钥签名、公钥验签。

相关链接: https://www.cnblogs.com/pcheng/p/9629621.html

方案一具体实现

前端

1、aes加密采用Crypto.js插件

封装crypro.js

  1. import { aesKey, aesIV } from './config'
  2. const CryptoJS = require('crypto-js'); //引用AES源码js
  3. // AES/CBC/PKCS7Padding 算法/模式/补码方式
  4. // 字符集 utf-8
  5. // mode 支持 CBC CFB CTR ECB OFB 默认CBC
  6. // padding 支持 Pkcs7 ZeroPadding NoPadding ... 默认 Pkcs7 即 Pkcs5
  7. // 前端 AES/CBC/Pkcs7 + iv
  8. // 后端 AES/CBC/Pkcs5 + iv
  9. const key = CryptoJS.enc.Utf8.parse(aesKey); //十六位十六进制数作为密钥
  10. const iv = CryptoJS.enc.Utf8.parse(aesIV); //十六位十六进制数作为密钥偏移量
  11. //加密方法
  12. export const aesEncrypt = (data, k) => {
  13. const key = CryptoJS.enc.Utf8.parse(k || aesKey)
  14. let encrypted = CryptoJS.AES.encrypt(data, key, {
  15. iv: iv,
  16. mode: CryptoJS.mode.CBC,
  17. padding: CryptoJS.pad.Pkcs7,
  18. })
  19. // 转换为字符串
  20. return encrypted.toString()
  21. }
  22. //解密方法
  23. export const aesDecrypt = (data, k) => {
  24. const key = CryptoJS.enc.Utf8.parse(k || aesKey)
  25. let decrypted = CryptoJS.AES.decrypt(data, key, {
  26. iv: iv,
  27. mode: CryptoJS.mode.CBC,
  28. padding: CryptoJS.pad.Pkcs7,
  29. })
  30. // 转换为 utf8 字符串
  31. let decryptedStr = decrypted.toString(CryptoJS.enc.Utf8)
  32. return decryptedStr
  33. }

2、rsa采用jsencrypt.js插件

封装 jsencrypt.js

  1. // 非对称加密 RSA
  2. import JSEncrypt from "jsencrypt";
  3. import { rsaPubKey, rsaPriKey } from "./config";
  4. // 公钥加密
  5. export const rsaEncrypt = (data, key) => {
  6. const encryptor = new JSEncrypt() // 创建加密对象实例
  7. // 设置公钥
  8. encryptor.setPublicKey(key || rsaPubKey)
  9. // 加密
  10. const rsaCipher = encryptor.encrypt(data)
  11. return rsaCipher
  12. }
  13. // 私钥解密
  14. export const rsaDecrypt = (ciphertext, key) => {
  15. const decrypt = new JSEncrypt() // 创建解密对象实例
  16. // 设置私钥
  17. decrypt.setPrivateKey(key || rsaPriKey)
  18. const oriData = decrypt.decrypt(ciphertext)
  19. return oriData
  20. }

3、请求加解密封装,以及重发逻辑处理

  1. /**
  2. * 封装axios
  3. * aes + rsa
  4. * 1、请求前body参数加密(不针对query参数)
  5. * VUE_APP_ENCRY '1'-加密 '0'-明文传输,便于查看参数
  6. * 2、获取响应数据,解密处理,判断 res.headers.keycipher 是否需要解密
  7. * 密文: 前端获取一律需要解密转 json
  8. */
  9. import { Message } from 'element-ui';
  10. import axios from 'axios'
  11. import moment from "moment";
  12. import { isObject, isString, getItem } from './utils';
  13. import { aesKey, inUseMockdata } from "./config";
  14. import { aesEncrypt, aesDecrypt } from './crypto';
  15. import { rsaEncrypt, rsaDecrypt } from './jsencrypt';
  16. axios.defaults.headers.post["Content-Type"] = "application/json; charset=UTF-8"
  17. // 1. 创建新的axios实例,
  18. const instance = axios.create({
  19. baseURL: !inUseMockdata ? process.env.VUE_APP_BASEURL + process.env.VUE_APP_PREURL : '',
  20. // `withCredentials`指示是否跨站点访问控制请求
  21. withCredentials: true,
  22. // "responseType"表示服务器将响应的数据类型
  23. // 包括 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
  24. responseType: 'json',
  25. // headers`是要发送的自定义 headers
  26. headers: {
  27. // 'X-Requested-With': 'XMLHttpRequest',
  28. 'Cache-Control': 'no-store' // IE 禁用缓存 // 'no-cache'
  29. },
  30. // 超时时间 单位是ms,这里设置了10s的超时时间
  31. timeout: 10 * 1000,
  32. transformRequest: [
  33. function (data, headers) {
  34. // 这里没有对 Form-Data 格式的报文处理
  35. if (isObject(data)) {
  36. // 一、请求参数加密
  37. if (process.env.VUE_APP_RUNTIME === 'prod') {
  38. data = JSON.stringify(data)
  39. headers["keyCipher"] = rsaEncrypt(aesKey) // 传输 aes key 密文
  40. data = aesEncrypt(data) // 加密请求参数
  41. }
  42. return data
  43. }
  44. return data
  45. }
  46. ],
  47. transformResponse: [
  48. function (data, headers) {
  49. if (isString(data)) {
  50. try {
  51. // 先对 axios 返回的源数据处理
  52. data = JSON.parse(data)
  53. /**
  54. * 二、获取响应数据之后解密
  55. * 判断 headers.keycipher 是否需要解密 (后端在接口报错的情况下,直接返回的是明文,不对错误信息加密)
  56. * 1、rsa 解密后端生成的 aes key
  57. * 2、aes 解密返参密文
  58. */
  59. const { keycipher = '' } = headers || {}
  60. if (keycipher) {
  61. // 解密
  62. const resAesKey = rsaDecrypt(keycipher)
  63. const dataStr = aesDecrypt(data, resAesKey) || '{}'
  64. data = JSON.parse(dataStr)
  65. }
  66. console.log("res data ====", data);
  67. } catch (err) {
  68. console.log("transformResponse-err", err);
  69. }
  70. return data
  71. }
  72. }
  73. ]
  74. })
  75. const codeMessage = {
  76. 200: '服务器成功返回请求的数据。',
  77. 201: '新建或修改数据成功。',
  78. 202: '一个请求已经进入后台排队(异步任务)。',
  79. 204: '删除数据成功。',
  80. 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
  81. 401: '用户没有权限(令牌、用户名、密码错误)。',
  82. 403: '用户得到授权,但是访问是被禁止的。',
  83. 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
  84. 405: '请求方法未允许',
  85. 406: '请求的格式不可得。',
  86. 408: '请求超时',
  87. 410: '请求的资源被永久删除,且不会再得到的。',
  88. 413: '上传文件过大',
  89. 422: '当创建一个对象时,发生一个验证错误。',
  90. 500: '服务器发生错误,请检查服务器。',
  91. 501: '网络未实现',
  92. 502: '网关错误。',
  93. 503: '服务不可用,服务器暂时过载或维护。',
  94. 504: '网关超时。',
  95. 505: 'http版本不支持该请求'
  96. };
  97. // 2. 添加请求拦截器
  98. instance.interceptors.request.use(config => {
  99. if (config.url) {
  100. config.headers = {
  101. ...config.headers,
  102. timeStamp: new Date().getTime(), // 毫秒数
  103. token: getItem("authToken") || ""
  104. }
  105. // 必须为开发环境,api内的mock开关开启才生效
  106. if (process.env.NODE_ENV === "development" && inUseMockdata && config.mock && config.mockUrl) {
  107. // mock 生效路径
  108. config.url = config.mockUrl
  109. }
  110. // 请求路径增加时间戳,防止命中缓存
  111. config.url += `?timeStamp=${config.headers.timeStamp}`
  112. return config;
  113. } else {
  114. return Promise.reject('接口不合法');
  115. }
  116. }, error => {
  117. // 对请求错误做些什么
  118. console.log('request-err', error);
  119. Message.error(error);
  120. return Promise.reject(error);
  121. });
  122. let retry = 2 // 重发次数
  123. let retryDelay = 200 // 重发时延
  124. // 3. 添加响应拦截器
  125. instance.interceptors.response.use(response => {
  126. // 对响应数据做点什么
  127. return response.data;
  128. }, error => {
  129. // 对响应错误做点什么
  130. console.log('response-err', error);
  131. /*****
  132. * 接收到异常响应的处理开始
  133. * 跨域存在获取不到状态码的情况
  134. * *****/
  135. if (error && error.response) {
  136. // 1.公共错误处理
  137. // 2.根据响应码具体处理
  138. const { status, config, statusText } = error.response;
  139. if (status) {
  140. const errorText = codeMessage[status] || statusText || '出错了';
  141. Message.error(`请求错误 ${status}: ${config.url}\n${errorText}`);
  142. } else {
  143. Message.error('您的网络发生异常,无法连接服务器');
  144. }
  145. } else {
  146. // 其它异常处理
  147. console.log("error!: " + JSON.stringify(error));
  148. // 重发逻辑
  149. let { config = {} } = error || {}
  150. // 记录单个api的请求次数
  151. config.requestCount = config.requestCount || 1
  152. if (config.requestCount < retry) {
  153. config.requestCount++ // 重发次数累加
  154. let backoff = new Promise((resolve) => {
  155. setTimeout(() => {
  156. resolve()
  157. }, retryDelay || 100);
  158. })
  159. // 重发 更新时间戳
  160. const retryUrl = config.url.split("?")[0] + `?timeStamp=${new Date().getTime()}`
  161. config.url = retryUrl
  162. console.log("retry config ===", config);
  163. return backoff.then(() => {
  164. return axios.request(config)
  165. })
  166. } else {
  167. Message({
  168. message: "连接服务器失败,请稍后再试",
  169. type: "error"
  170. })
  171. }
  172. /* 超时处理
  173. 无法通过判断是否存在 timeout 字符,来确定服务是否连接超时
  174. 因为 error 信息里包含请求的 config 信息,里面配置的 timeout 字段 */
  175. if (JSON.stringify(error).toLocaleLowerCase().includes('timeout')) {
  176. Message.error('服务器响应超时,请刷新当前页');
  177. } else {
  178. Message({
  179. message: '连接服务器失败',
  180. type: "error"
  181. })
  182. }
  183. }
  184. /***** 处理结束 *****/
  185. return Promise.reject(error);
  186. });
  187. //4.导出文件
  188. export default instance

后端