项目场景:
在前后端交互的过程中,为了保障数据的安全性,往往会对数据进行加密,这篇就记录一下自己实现加解密的方法
实现效果:
提示:仅实现带有请求体的请求的加密,不支持没有请求体的请求,如get类的请求
(1)请求体加密效果:
(2)响应体加密效果:
前端代码:
提示:
1、加解密函数
import CryptoJS from 'crypto-js'
// 如果没有则npm引入,命令为:npm install crypto-js --save-dev
// 需要和后端一致
const KEY = CryptoJS.enc.Utf8.parse('1234567890123456');
const IV = CryptoJS.enc.Utf8.parse('ABCDEFG123456789');
/**
* 加密
* 若未指定密钥和偏移量则使用默认的
* @param {*} info
* @param {*} keyStr
* @param {*} ivStr
*/
const encrypt = (info: any, keyStr?: any, ivStr?: any) => {
let key = KEY;
let iv = IV;
if (keyStr) {
key = CryptoJS.enc.Utf8.parse(keyStr);
iv = CryptoJS.enc.Utf8.parse(ivStr);
}
let srcs = CryptoJS.enc.Utf8.parse(info);
var encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
});
return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
}
/**
* 解密
* 若未指定密钥和偏移量则使用默认的
* @param {*} info
* @param {*} keyStr
* @param {*} ivStr
*/
const decrypt = (info: any, keyStr?: any, ivStr?: any) => {
let key = KEY;
let iv = IV;
if (keyStr) {
key = CryptoJS.enc.Utf8.parse(keyStr);
iv = CryptoJS.enc.Utf8.parse(ivStr);
}
let base64 = CryptoJS.enc.Base64.parse(info);
let src = CryptoJS.enc.Base64.stringify(base64);
let decrypt = CryptoJS.AES.decrypt(src, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
});
let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
return decryptedStr.toString();
}
export default {
encrypt,
decrypt
}
2、引入所写的工具函数
在统一封装的axios请求文件中引入
注:请求头的Content-Type需为:application/json;charset=UTF-8
import asc from '@/utils/asc'
3、请求前置拦截器
/**
* 前置拦截器
*/
instance.interceptors.request.use((config) => {
// 数据传输加密
config.data = asc.encrypt(JSON.stringify(config.data));
return config;
}, (error: any) => {
//...
return error;
})
4、请求后置拦截器
instance.interceptors.response.use((res: AxiosResponse) => {
if (res.status == 200) {
//使用replace清除数据中的空格和换行,以防万一
let resData = JSON.parse(asc.decrypt(res.data.replace(/\s/g,'')));
// 若无后端封装的Result对象,则直接返回res
if (!resData.hasOwnProperty('data')) {
return res;
}
return resData;
}
}else{
// ...
// return ...
}
}, (error: any) => {
//...
return error;
})
后端代码:
提示:
1、加解密工具类
import org.apache.tomcat.util.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* @author 陶祎祎
*/
public class AscUtils {
/***
* key和iv值需要和前端一致
*/
public static final String KEY = "1234567890123456";
public static final String IV = "ABCDEFG123456789";
/**
* 加密方法 默认key和iv
* 返回字符串
* @param data 要加密的数据
* @return 加密的结果
*/
public static String encryptReturnStr(String data) {
try {
//"算法/模式/补码方式"NoPadding PkcsPadding
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
int blockSize = cipher.getBlockSize();
//此处的'utf-8'不可或缺,前后端数据格式要对应;如果缺少了utf-8,而程序的file.encoding又没设置成utf-8,前端解密的时候会拿不到正确数据,因为前端解密时是以utf-8的格式解密的,可见上文:decrypt.toString(CryptoJS.enc.Utf8);
//生产环境下设置file.encoding的方式如下
//jar包启动,需要指定为: java -jar -Dfile.encoding=utf-8 jar_name.jar
//Tomcat启动的,需要在Tomcat的catalina.bat中设置,不详细写了,遇到这个问题就自己去查吧
byte[] dataBytes = data.getBytes("utf-8");
int plaintextLength = dataBytes.length;
if (plaintextLength % blockSize != 0) {
plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
}
byte[] plaintext = new byte[plaintextLength];
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
SecretKeySpec keyspec = new SecretKeySpec(KEY.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(IV.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(plaintext);
return new Base64().encodeToString(encrypted);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 解密 默认key和iv
* 返回字节数组
* @param data
* @return
*/
public static byte[] descryptReturnByte(String data) {
try {
byte[] encrypted1 = new Base64().decode(data);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] original = cipher.doFinal(encrypted1);
return original;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
2、自定义请求实现类
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import java.io.IOException;
import java.lang.reflect.Type;
/**
* 只对 @RequestBody 参数有效
* @author 陶祎祎
*/
@ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
return new DecryptHttpInputMessage(inputMessage);
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
3、自定义响应实现类
import cn.edu.guet.util.AscUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* @author 陶祎祎
*/
@ControllerAdvice
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
// 此处返回true表示对任何handler的responseBody都调用beforeBodyWrite方法,此次可使用自定义注解来判断哪些方法需要加密
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 由于实体bean中使用了@JsonFormat注解,因此需使用ObjectMapper进行序列化,而不能使用其他json工具进行转换,如fastjson
String jsonString="";
try {
jsonString = objectMapper.writeValueAsString(body);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
return AscUtils.encryptReturnStr(jsonString);
// 不需要进行任何操作则直接返回body
// return body;
}
}
4、自定义请求信息解密类
import cn.edu.guet.util.AscUtils;
import cn.hutool.core.io.IoUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @author 陶祎祎
*/
@Data
@AllArgsConstructor
public class DecryptHttpInputMessage implements HttpInputMessage {
private HttpInputMessage inputMessage;
@Override
public InputStream getBody() throws IOException {
if(inputMessage.getBody() == null){
return null;
}
String content = IoUtil.readUtf8(inputMessage.getBody());
byte[] bytes = AscUtils.descryptReturnByte(content);
return new ByteArrayInputStream(bytes);
}
@Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
}