前vue后springboot端统一修改请求与响应中的标头及body的方法(双向加解密数据传输)

直接上代码

一、结果

 

  

二、过程

vue 端 request.js 核心代码,加解密工具类,请见前面的贴子

  1 // request拦截器
  2 service.interceptors.request.use(config => {
  3   // 是否需要设置 token
  4   const isToken = (config.headers || {}).isToken === false
  5   // 是否需要防止数据重复提交
  6   const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
  7   if (getToken() && !isToken) {
  8     config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
  9   }
 10   // get请求映射params参数
 11   if (config.method === 'get' && config.params) {
 12     let url = config.url + '?' + tansParams(config.params)
 13     url = url.slice(0, -1)
 14     config.params = {}
 15     config.url = url
 16   }
 17 
 18   // 1.动态生成密钥 des3 key
 19   const DES3_KEY_SIZE = 24
 20   let des3Key = genKey(DES3_KEY_SIZE, true)
 21   // console.log("des3Key:",des3Key);
 22 
 23   // 2.key 经过rsa ,服务器端公钥加密,再base64, 得到密文key:SecurityKey
 24   let SecurityKey = RsaEncrypts(des3Key, true, true, false)
 25 
 26   // 3.设置header:SecurityKey
 27   config.headers['SecurityKey'] = SecurityKey
 28   // console.log("SecurityKey:",SecurityKey);
 29 
 30   // console.log("config.data:",config.data);
 31   let plainData = JSON.stringify(config.data)
 32   // console.log("plainData:", plainData);
 33   let cipherData = ''
 34   // 4.将传输body数据des3加密,生成密文body,再base64,再urlcode
 35   if (config.data != 'undefined') {
 36     cipherData = encrypt3DES('TripleDES', plainData, des3Key, CryptoJS.mode.CBC, CryptoJS.pad.Pkcs7, true, true, false)
 37     //  console.log("cipherData:", cipherData);
 38   }
 39 
 40   // 5. 将密文body,经过base64, SHA256 生成摘要,客户端私钥生成签名,再base64
 41   let Authentication = RsaSignAuthentication(cipherData, 'PKCS#7', 'SHA256withRSA', true, true, false)
 42 
 43   // 6.设置header:Authentication
 44   config.headers['Authentication'] = Authentication
 45   // console.log("Authentication:",Authentication);
 46   // 7.避免特殊字符丢失 urlcode
 47   cipherData = encodeURIComponent(cipherData)
 48   config.data = cipherData
 49 
 50   if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
 51     let requestObj = {
 52       url: config.url,
 53       data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
 54       time: new Date().getTime()
 55     }
 56     // console.log("发送数据:",requestObj.data);
 57 
 58     const sessionObj = cache.session.getJSON('sessionObj')
 59     if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
 60       cache.session.setJSON('sessionObj', requestObj)
 61     } else {
 62       const s_url = sessionObj.url                  // 请求地址
 63       const s_data = sessionObj.data                // 请求数据
 64       const s_time = sessionObj.time                // 请求时间
 65       const interval = 1000                         // 间隔时间(ms),小于此时间视为重复提交
 66       if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
 67         const message = '数据正在处理,请勿重复提交'
 68         console.warn(`[${s_url}]: ` + message)
 69         return Promise.reject(new Error(message))
 70       } else {
 71         cache.session.setJSON('sessionObj', requestObj)
 72       }
 73     }
 74   }
 75   return config
 76 }, error => {
 77   console.log(error)
 78   Promise.reject(error)
 79 })
 80 
 81 // 响应拦截器
 82 service.interceptors.response.use(response => {
 83     // 未设置状态码则默认成功状态
 84     const code = response.data.code || 200
 85     // 获取错误信息
 86     const message = errorCode[code] || response.data.message || errorCode['default']
 87 
 88     // 注意服务器端有大写字符,但前端接收返回全部小写
 89     let authentication = response.headers['authentication']
 90     let securityKey = response.headers.securitykey
 91     // 不要用console.log("响应结果:"+response.headers); 返回[object object]
 92     // console.log("响应body:",response.data);
 93 
 94     if (authentication) {
 95       let cipherData = decodeURIComponent(response.data)
 96       // console.log('接收到服务器端返回body密文:', cipherData)
 97       let verifyAuthentication = RsaVerifyAuthentication(cipherData, authentication, 'PKCS#7', 'MD5withRSA', true,
 98         true, false)
 99       //console.log('通过服务器端的公钥,验签结果:' + verifyAuthentication.toString())
100       if (verifyAuthentication) {
101         // let key = '1234567890123456'
102         // let aesKey = Base64.encode(key);
103 
104         //  aesKey ="8A5EzyHtJjklPj4a9NWwXw==";
105         // console.log('测试,得到aeskey:' + aesKey)
106 
107         let aesKey = RsaDecrypts(securityKey, true, true, false)
108         // console.log('通过客户端的私钥解密得到明文key:', aesKey)
109 
110         //  let plain = '依芸 abcdef 123'
111         // let cipherData = encryptAES('AES', plain, aesKey, CryptoJS.mode.CBC, CryptoJS.pad.Pkcs7, true, true, false)
112         // console.log('测试,加密得到密文数据:', cipherData)
113 
114         // let plainData = decryptAES('AES', cipherData, aesKey, CryptoJS.mode.CBC, CryptoJS.pad.Pkcs7, true, true, false)
115         // console.log('测试,解密得到明文数据:', plainData)
116 
117         let plainData = decryptAES('AES', cipherData, aesKey, CryptoJS.mode.CBC, CryptoJS.pad.Pkcs7, true, true, false)
118         // console.log('通过解密密钥后再aes解密body密文得到明文body:', plainData)
119 
120         response.data = JSON.parse(plainData)
121         // 转换成json对象
122       }
123     }
124 
125     // 二进制数据则直接返回
126     if (response.request.responseType === 'blob' || response.request.responseType === 'arraybuffer') {
127       return response.data
128     }
129 
130     if (code === 401) {
131       if (!isReloginShow) {
132         isReloginShow = true
133         MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
134             confirmButtonText: '重新登录',
135             cancelButtonText: '取消',
136             type: 'warning'
137           }
138         ).then(() => {
139           isReloginShow = false
140           store.dispatch('LogOut').then(() => {
141             // 如果是登录页面不需要重新加载
142             if (window.location.hash.indexOf('#/login') != 0) {
143               location.href = '/index'
144             }
145           })
146         }).catch(() => {
147           isReloginShow = false
148         })
149       }
150       return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
151     } else if (code === 500) {
152       Message({
153         message: message,
154         type: 'error'
155       })
156       return Promise.reject(new Error(message))
157     } else if (code !== 200) {
158       Notification.error({
159         title: message
160       })
161       return Promise.reject('error')
162     } else {
163       return response.data
164     }
165   },
166   error => {
167     console.log('err' + error)
168     let { message } = error
169     if (message == 'Network Error') {
170       message = '后端接口连接异常'
171     } else if (message.includes('timeout')) {
172       message = '系统接口请求超时'
173     } else if (message.includes('Request failed with status code')) {
174       message = '系统接口' + message.substr(message.length - 3) + '异常'
175     }
176     Message({
177       message: message,
178       type: 'error',
179       duration: 5 * 1000
180     })
181     return Promise.reject(error)
182   }
183 )
View Code

spring boot 端 核心代码  加解密工具类,请见前面的贴子

DecodeRequestBodyAdvice.java

  1 package com.ruoyi.framework.interceptor;
  2 
  3 import com.ruoyi.common.constant.Constants;
  4 import com.ruoyi.common.utils.enDeCrypt.RsaUtil;
  5 import com.ruoyi.common.utils.enDeCrypt.TripleDesUtil;
  6 import com.ruoyi.common.utils.enDeCrypt.UrlCode;
  7 import lombok.SneakyThrows;
  8 import org.springframework.core.MethodParameter;
  9 import org.springframework.http.HttpHeaders;
 10 import org.springframework.http.HttpInputMessage;
 11 import org.springframework.http.converter.HttpMessageConverter;
 12 import org.springframework.web.bind.annotation.ControllerAdvice;
 13 import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
 14 
 15 import java.io.*;
 16 import java.lang.reflect.Type;
 17 import java.nio.charset.StandardCharsets;
 18 
 19 /**
 20  * @author x8023z
 21  */
 22 @ControllerAdvice(basePackages = "com.ruoyi.web.controller")
 23 public class DecodeRequestBodyAdvice implements RequestBodyAdvice {
 24     @Override
 25     public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
 26         //true开启功能,false关闭这个功能
 27         return true;
 28     }
 29 
 30     @Override
 31     public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
 32         return body;
 33     }
 34 
 35     //在读取请求之前处理
 36     @SneakyThrows
 37     @Override
 38     public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
 39         if (httpInputMessage.getBody().available() <= 0) {
 40             return httpInputMessage;
 41         }
 42         String SecurityKey = httpInputMessage.getHeaders().get("SecurityKey").get(0);
 43         //System.out.println("SecurityKey:"+SecurityKey);
 44         String Authentication = httpInputMessage.getHeaders().get("Authentication").get(0);
 45         //  System.out.println("Authentication:"+Authentication);
 46 
 47         byte[] requestDataByte = new byte[httpInputMessage.getBody().available()];
 48         httpInputMessage.getBody().read(requestDataByte);
 49         byte[] requestDataByteNew = null;
 50         try {
 51             // 解密
 52             requestDataByteNew = this.decryptBody(requestDataByte, SecurityKey, Authentication);
 53         } catch (IOException e) {
 54             throw new RuntimeException("解密异常");
 55         }
 56         // 使用解密后的数据,构造新的读取流
 57         InputStream rawInputStream = new ByteArrayInputStream(requestDataByteNew);
 58         return new HttpInputMessage() {
 59             @Override
 60             public HttpHeaders getHeaders() {
 61                 return httpInputMessage.getHeaders();
 62             }
 63 
 64             @Override
 65             public InputStream getBody() throws IOException {
 66                 return rawInputStream;
 67             }
 68         };
 69     }
 70 
 71 
 72     /**
 73      * 在Http消息转换器执转换,之后执行
 74      * body 转换后的对象
 75      * inputMessage 客户端的请求数据
 76      * parameter handler方法的参数类型
 77      * targetType handler方法的参数类型
 78      * converterType 使用的Http消息转换器类类型
 79      *
 80      * @return 返回一个新的对象
 81      */
 82     @Override
 83     public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
 84         return body;
 85     }
 86 
 87 
 88     /**
 89      * SHA256签名验证,DES3解密
 90      *
 91      * @param requestBytes
 92      * @return
 93      */
 94     public byte[] decryptBody(byte[] requestBytes, String SecurityKey, String Authentication) throws Exception {
 95         byte[] byteResult = new byte[0];
 96         if (requestBytes.length <= 0) {
 97             return byteResult;
 98         }
 99 
100         //1. urldecode 得到base64 body
101         String requestData = new String(requestBytes, StandardCharsets.UTF_8);
102         UrlCode urlCode = new UrlCode();
103         String urlDecodeData = urlCode.decodeURL(requestData, StandardCharsets.UTF_8).trim();
104         // System.out.println("密文body:"+urlDecodeData);
105 
106         RsaUtil rsaUtil = null;
107         String des3Key = "";
108 
109         rsaUtil = new RsaUtil(true, Constants.RSA_CBC_PKCS7, Constants.SHA256WITHRSA);
110         boolean isVerifyPassed = rsaUtil.verifySignPublicKey(urlDecodeData, Authentication, Constants.PUBLICKEY_C, true, true, false);
111         //System.out.println("isVerifyPassed:"+isVerifyPassed);
112 
113         //2.先sha256withrsa 验证签名再解密密钥,再des3解密数据
114         String plainBody = "";
115         if (isVerifyPassed) {
116             rsaUtil = new RsaUtil(true);
117             //3.解密des3key  rsa RSA_PKCS1_PADDING 解密
118             des3Key = rsaUtil.decryptByPrivateKey(SecurityKey, Constants.PRRIVATEKEY_S, true, true, false);
119             // System.out.println("des3Key:"+des3Key);
120             if (des3Key != null) {
121                 //4. des3解密body
122                 TripleDesUtil tripleDes = new TripleDesUtil(urlDecodeData, Constants.DESEDE, des3Key, Constants.DES3_KEY_SIZE, Constants.DES3_IV, Constants.DES3_CBC_PKCS7, Constants.UTF8, true, true);
123                 plainBody = tripleDes.decode();
124                 // System.out.println("plainBody:"+plainBody);
125                 //验证通过,返回解密后的参数
126                 return plainBody.getBytes(StandardCharsets.UTF_8);
127             } else {
128                 //解密des3key异常
129                 throw new RuntimeException("解密密钥异常");
130             }
131         } else {
132             throw new RuntimeException("验签异常");
133             //验签异常
134         }
135     }
136 }
View Code

EncodeResponseBodyAdvice.java

 1 package com.ruoyi.framework.interceptor;
 2 
 3 import com.alibaba.fastjson.JSON;
 4 import com.alibaba.fastjson.serializer.SerializerFeature;
 5 import com.ruoyi.common.constant.Constants;
 6 import com.ruoyi.common.utils.enDeCrypt.AesUtil;
 7 import com.ruoyi.common.utils.enDeCrypt.RsaUtil;
 8 import lombok.SneakyThrows;
 9 import org.springframework.core.MethodParameter;
10 import org.springframework.http.MediaType;
11 import org.springframework.http.server.ServerHttpRequest;
12 import org.springframework.http.server.ServerHttpResponse;
13 import org.springframework.web.bind.annotation.ControllerAdvice;
14 import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
15 import java.net.URLEncoder;
16 
17 /**
18  * @author EvianZou
19  */
20 @ControllerAdvice(basePackages = "com.ruoyi.web.controller")
21 public class EncodeResponseBodyAdvice implements ResponseBodyAdvice {
22 
23     @Override
24     public boolean supports(MethodParameter methodParameter, Class aClass) {
25         return true;
26     }
27 
28     @SneakyThrows
29     @Override
30     public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
31         String body = JSON.toJSONString(o, SerializerFeature.DisableCircularReferenceDetect);
32 
33         RsaUtil rsaUtil = new RsaUtil(true);
34         AesUtil aesUtil = new AesUtil();
35 
36         // 密钥:动态生成AESKey,RSA客户端公钥加密,再base64
37         rsaUtil.setSignAlgorithm(Constants.SHA1WITHRSA);
38         rsaUtil.setPaddingMode(Constants.RSA_ECB_PKCS1);
39 
40         String aesKey = aesUtil.getAesSecretKey(Constants.AES, Constants.PASSWORD, true);
41         String securityKey = rsaUtil.encryptByPublicKey(aesKey, Constants.PUBLICKEY_C, true, true, false);
42         // System.out.println("通过客户端公钥rsa加密密钥得到密文key:" + securityKey);
43 
44         // 服务器端作为发送方
45         // 签名 传输数据转json字符串,base64,MD5摘要,RSA服务器端私钥生成签名,base64
46         rsaUtil.setSignAlgorithm(Constants.MD5WITHRSA);
47         rsaUtil.setPaddingMode(Constants.RSA_ECB_PKCS7);
48 
49         // 密钥、密文 是否utf8,是否base64,是否hex 都影响前后端加解密是否一致。jdk1.8以下版本默认不支持aes密钥长度为256
50         String cipherData = aesUtil.encode(body, aesKey, Constants.AES_CBC_PKCS7, Constants.AES_IV, true, true, false);
51         // System.out.println("通过明文key加密body得到密文body:"+cipherData);
52 
53         /*
54         //String  plainText = aesUtil.decode(cipherText,aesKey,Constants.AES_CBC_PKCS7,Constants.AES_IV,true,true,false);
55         //System.out.println("通过明文key解密body得到明文body:" + plainText);
56 
57         // aesKey="1234567890123456";
58         // aesKey = DatatypeConverter.printBase64Binary(aesKey.getBytes(StandardCharsets.UTF_8));
59         //  aesKey = "8A5EzyHtJjklPj4a9NWwXw==";
60         // System.out.println("测试,得到aesKey:" + aesKey);
61 
62         // String test = aesUtil.encode("邹依芸 abcdef 123",aesKey,Constants.AES_CBC_PKCS7,Constants.AES_IV,true,true,false);
63         // System.out.println("测试,加密得到密文数据:" + test);
64 
65         //  test = aesUtil.decode(test,aesKey,Constants.AES_CBC_PKCS7,Constants.AES_IV,true,true,false);
66         // System.out.println("测试,解密得到明文数据:" + test);
67         */
68 
69         String authentication = rsaUtil.signByPrivateKey(cipherData, Constants.PRRIVATEKEY_S, true, true, false);
70         // System.out.println("通过服务器端的私钥key生成摘要得到签名" + authentication);
71 
72         serverHttpResponse.getHeaders().set("Access-Control-Expose-Headers", "Authentication,SecurityKey");
73 
74         // 密钥:动态生成AESKey,RSA客户端公钥加密,再base64
75         serverHttpResponse.getHeaders().set("SecurityKey", securityKey);
76 
77         // 服务器端作为发送方
78         // 签名 传输数据转json字符串,base64,MD5摘要,RSA服务器端私钥生成签名
79         serverHttpResponse.getHeaders().set("Authentication", authentication);
80 
81         // 不直接返回加密字符串,因为控制层使用了@ResponseBody注解,直接返回字符串会在前后各多出一个双引号
82 
83         // 解决数据传输丢失特殊符号问题:如url特殊字符, 丢失+号,+号变成了空格
84         return URLEncoder.encode(cipherData,Constants.UTF8);
85     }
86 
87 }
View Code

三、原理

1、springboot: 切面

 

RequestBodyAdvice 可以理解为在 @RequestBody 之前需要进行操作,ResponseBodyAdvice 可以理解为在 @ResponseBody 之后进行的操作,所以当接口需要加解密时,在使用 @RequestBody 接收前台参数之前可以先在 RequestBodyAdvice 的实现类中进行参数的解密,当操作结束需要返回数据时,可以在@ResponseBody之后进入ResponseBodyAdvice的实现类中进行参数的加密。

2、Vue中使用axios封装的全局拦截器,拦截器(英文:Interceptors)会在每次发起 ajax 请求和得到响应的时候自动被触发。

3、加解密逻辑说明

posted @ 2022-01-12 13:59  HiEagle  阅读(786)  评论(0编辑  收藏  举报