一、前言:
数据是企业的第四张名片,企业级开发中少不了数据的加密传输。为了预防请求数据被劫持篡改,一般都会对传输的数据进行加密操作,如果每个接口都由我们自己去手动加密和解密,那么工作量太大而且代码冗余。那么有没有简单的方法,借助 Spring 提供的 RequestBodyAdvice 和 ResponseBodyAdvice 可以实现解密和加密操作。
二、实现原理:
RequestBodyAdvice处理请求的过程:
RequestBodyAdvice源码如下:
查看读取主体的内容来解析方法参数值的基类 AbstractMessageConverterMethodArgumentResolver 部分代码:
从上面源码可以到当 converter.canRead() 和 message.hasBody() 都为true的时候,会调用beforeBodyRead()和afterBodyRead()方法,所以我们在实现类的 afterBodyRead() 中编写解密代码即可。
ResponseBodyAdvice处理响应的过程:
ResponseBodyAdvice源码如下:
查看控制器方法参数和返回值处理设置类 AbstractMessageConverterMethodProcessor 部分代码:
从上面源码可以到当converter.canWrite()为true的时候,会调用beforeBodyWrite()方法,所以我们在实现类的beforeBodyWrite()中添加解密代码即可。
结论:
RequestBodyAdvice
可以理解为在@RequestBody
之前需要进行操作,ResponseBodyAdvice
可以理解为在@ResponseBody
之后进行的操作,所以当接口需要加解密时,在使用@RequestBody
接收前台参数之前可以先在RequestBodyAdvice
的实现类中进行参数的解密,当操作结束需要返回数据时,可以在@ResponseBody
之后进入ResponseBodyAdvice
的实现类中进行参数的加密。
注意:当方法参数里面有 HttpServeltResponse 时, ResponseBodyAdvice 将不会生效, 使用response返回数据。
三、新在哪里:
-
采用RSA非对称加密,但由于RSA非对称加密只能加密 < 117 字节的字符串,并不满足需求,所有自写了一个数据分段加/解密的方法
segmentDataPretreatment(int maxLength, byte[] data, Cipher cipher)
, 实现 > 117 字节的字符串加/解密。 -
ResponseBodyAdvice
只支持带有@ResponseBody
注解的方法,RequestBodyAdvice
只支持带有@RequestBody
注解的方法。 这样有个弊端,就是我并不是想每个响应体都加密,而是只对几个特殊重要的接口进行加密,但数据响应体又离不开@ResponseBody
, 导致响应的数据都给加密了,增加服务器负担。这里我采用自定义注解@ResponseEncrypt
,只有带有我自定义的注解的方法才会被加密,这样就达到想加密就加密哪里啦~ -
代码里面还多次使用 Base64 加密,更大程度上增大被破解的难度。
-
…
四、实践:
4.1、新建一个spring boot项目 springboot-encrypt
4.2 、Pom.xml 中的 jar 引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
4.3、代码编写 (详情请看注释)
application.yml
keysUrl:
pubKeyFileUrl: C:\securityKey\rsaPub.txt # 公钥路径
priKeyFileUrl: C:\securityKey\rsaPri.txt # 私钥路径
secret: h$_*&^%sd # 盐值
RsaUtil 工具类
用于公钥私钥的生成,以及数据的加密解密,自写一套分段加密的逻辑代码
segmentDataPretreatment(int maxLength, byte[] data, Cipher cipher)
import com.xiao.springbootencrypt.config.RsaConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
*@ClassName: RSAUtil
*@Description: RSA公钥/私钥/加密解密
*@Author xxw
*@Date 2021/3/28
*/
@Component
public class RsaUtil {
// 加密算法 RSA
public static final String KEY_ALGORITHM = "RSA";
// RSA最大加密明文大小
private static final int MAX_ENCRYPT_BLOCK = 117;
// RSA最大解密密文大小
private static final int MAX_DECRYPT_BLOCK = 128;
// RSA 位数
private static final int INITIALIZE_LENGTH = 1024;
@Autowired
private RsaConfig rsaConfig;
/**
*@ClassName: RSAUtils
*@Description: 生成秘钥对: 公钥私钥
*@Params:
*@Return:
*@Author xxw
*@Date 2021/2/14
*/
public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
SecureRandom secureRandom = new SecureRandom(secret.getBytes()); // 用盐值生成加密基本的随机数
keyPairGenerator.initialize(INITIALIZE_LENGTH,secureRandom); // RSA 位数 + 随机数 初始化一个 KeyPairGenerator对象
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 生成公钥并存入文件
String publicKey = Base64Util.encode(keyPair.getPublic().getEncoded());
FileUtil.writeFile(publicKeyFilename, publicKey.getBytes());
// 生成私钥并存入文件
String privateKey = Base64Util.encode(keyPair.getPrivate().getEncoded());
FileUtil.writeFile(privateKeyFilename,privateKey.getBytes());
}
/**
*@ClassName: RSAUtil
*@Description: 从 Base64Util.encode(byte[](publicKey.getEncoded())) 还原回公钥,适用于RSA算法
*@Params:
*@Return:
*@Author xxw
*@Date 2021/3/28
*/
public static PublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
// X509EncodedKeySpec 返回按照 X.509 标准进行编码的密钥的字节。
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64Util.decode(publicKey));
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
return keyFactory.generatePublic(keySpec);
}
/**
*@ClassName: RSAUtil
*@Description: 从 Base64Util.encode(byte[](publicKey.getEncoded())) 将还原回私钥,适用于RSA算法
*@Params:
*@Return:
*@Author xxw
*@Date 2021/3/28
*/
public static PrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
// PKCS8EncodedKeySpec 返回按照 PKCS8 标准进行编码的密钥的字节。
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64Util.decode(privateKey));
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
return keyFactory.generatePrivate(keySpec);
}
/**
*@ClassName: RSAUtil
*@Description: 公钥加密
*@Params:
*@Return:
*@Author xxw
*@Date 2021/3/28
*/
public byte[] encryptData(byte[] data){
try {
// 获取 Base64 加密的公钥
String publicKey = FileUtil.readFile(rsaConfig.getPubKeyFileUrl());
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
// 编码前设定编码方式及密钥
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKey));
// 分段加密, 输出是 Bsae64 再加密一次,避免数据是二进制乱码
return Base64Util.encode(segmentDataPretreatment(MAX_ENCRYPT_BLOCK,data,cipher)).getBytes();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
*@ClassName: RSAUtil
*@Description: 私钥解密
*@Params:
*@Return:
*@Author xxw
*@Date 2021/3/28
*/
public byte[] decryptData(byte[] encryptedData){
// 先对数据进行Base64解密
encryptedData = Base64Util.decode(new String(encryptedData));
try {
String privateKey = FileUtil.readFile(rsaConfig.getPriKeyFileUrl());
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(privateKey));
// 分段解密
return segmentDataPretreatment(MAX_DECRYPT_BLOCK,encryptedData,cipher);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
*@ClassName: RsaUtil
*@Description: 分段数据加密解密预处理
*@Params: maxLength: 最大密/明文长度, data: 要分段的数据, ciper
*@Return:
*@Author xxw
*@Date 2021/3/28
*/
public static byte[] segmentDataPretreatment(int maxLength, byte[] data, Cipher cipher) throws BadPaddingException, IllegalBlockSizeException {
// 存储分段 dofinal 的加密后的数据
ArrayList<byte[]> list = new ArrayList<>();
// 传入数据并返回解密结果, 采用分段解密
for (int i = 0; i < data.length; i += maxLength) {
byte doFinal[];
byte tempArr[] = null;
// 判断是否超出 解密/加密的最大长度
if (data.length - i > maxLength) {
tempArr = new byte[maxLength];
System.arraycopy(data, i, tempArr, 0, maxLength);
doFinal = cipher.doFinal(tempArr);
} else {
tempArr = new byte[data.length - i];
System.arraycopy(data, i, tempArr, 0, data.length - i);
doFinal = cipher.doFinal(tempArr);
}
// 累加
list.add(doFinal);
}
// doFinal 总长度
int fLengthSum = list.stream().mapToInt(item -> item.length).sum();
System.out.println(fLengthSum);
// 定义处理后的数据
byte[] atfTreatData = new byte[fLengthSum];
// 目标数据中的起始位置
AtomicInteger desPos = new AtomicInteger();
// 加密结果合并
list.forEach(item -> {
// 累加
System.arraycopy(item, 0, atfTreatData, desPos.get(), item.length);
// 获取旧值并添加新值
desPos.addAndGet(item.length);
});
return atfTreatData;
}
}
Base64Util 工具类
用于Base64加密与解密
import java.io.UnsupportedEncodingException;
/**
*@ClassName: Base64Utils
*@Description: 用于 Base64 加密和 解密
*@Params:
*@Return:
*@Author xxw
*@Date 2021/3/28
*/
public class Base64Util {
private static char[] base64EncodeChars = new char[]
{ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', '+', '/' };
private static byte[] base64DecodeChars = new byte[]
{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53,
54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1,
-1, -1, -1 };
/**
* @param data
* @Description Base64 加密
* @return
*/
public static String encode(byte[] data) {
StringBuffer sb = new StringBuffer();
int len = data.length;
int i = 0;
int b1, b2, b3;
while (i < len)
{
b1 = data[i++] & 0xff;
if (i == len)
{
sb.append(base64EncodeChars[b1 >>> 2]);
sb.append(base64EncodeChars[(b1 & 0x3) << 4]);
sb.append("==");
break;
}
b2 = data[i++] & 0xff;
if (i == len)
{
sb.append(base64EncodeChars[b1 >>> 2]);
sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
sb.append(base64EncodeChars[(b2 & 0x0f) << 2]);
sb.append("=");
break;
}
b3 = data[i++] & 0xff;
sb.append(base64EncodeChars[b1 >>> 2]);
sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
sb.append(base64EncodeChars[((b2 & 0x0f) << 2) | ((b3 & 0xc0) >>> 6)]);
sb.append(base64EncodeChars[b3 & 0x3f]);
}
return sb.toString();
}
/**
* @param str
* @Description Base64 解密
* @return
*/
public static byte[] decode(String str) {
try {
return decodePrivate(str);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return new byte[]{};
}
private static byte[] decodePrivate(String str) throws UnsupportedEncodingException {
StringBuffer sb = new StringBuffer();
byte[] data = null;
data = str.getBytes("US-ASCII");
int len = data.length;
int i = 0;
int b1, b2, b3, b4;
while (i < len) {
do {
b1 = base64DecodeChars[data[i++]];
} while (i < len && b1 == -1);
if (b1 == -1) {
break;
}
do {
b2 = base64DecodeChars[data[i++]];
} while (i < len && b2 == -1);
if (b2 == -1) {
break;
}
sb.append((char) ((b1 << 2) | ((b2 & 0x30) >>> 4)));
do {
b3 = data[i++];
if (b3 == 61) {
return sb.toString().getBytes("iso8859-1");
}
b3 = base64DecodeChars[b3];
} while (i < len && b3 == -1);
if (b3 == -1) {
break;
}
sb.append((char) (((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2)));
do {
b4 = data[i++];
if (b4 == 61) {
return sb.toString().getBytes("iso8859-1");
}
b4 = base64DecodeChars[b4];
} while (i < len && b4 == -1);
if (b4 == -1) {
break;
}
sb.append((char) (((b3 & 0x03) << 6) | b4));
}
return sb.toString().getBytes("iso8859-1");
}
}
File.Util 工具类
用于文件的读写操作
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
/**
*@ClassName: FileUtil
*@Description: I/O 操作工具类
*@Params:
*@Return:
*@Author xxw
*@Date 2021/3/28
*/
public class FileUtil {
/*
* 文件写入
* */
public static void writeFile(String destPath, byte[] bytes) throws IOException {
File dest = new File(destPath);
if (!dest.getParentFile().exists()) {
// 没有目录先创建目录
dest.getParentFile().mkdir();
}
// 判断文件是否存在
if (!dest.exists()) {
dest.createNewFile();
}
Files.write(dest.toPath(), bytes);
}
/*
* 文件读取
* */
public static String readFile(String fileName) throws Exception {
return new String(Files.readAllBytes(new File(fileName).toPath()));
}
}
小问题:
一开始文件读取我是这么写的:
/*
* 文件读取
* */
public static String readFile(String fileName) throws Exception {
return Arrays.toString (Files.readAllBytes(new File(fileName).toPath()));
}
获取的秘钥是如下图所示:
都是 ASCII 码,结果加解密的时候就报了如下错误:
正确解决方法是不要是用 Arrays.toString()来转字符串,而是用 new String() 来转。
RsaConfig 配置类
用于 RsaConfig 读取 yml 文件,以及判断公钥私钥是否存在
import com.xiao.springbootencrypt.util.RsaUtil;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
@Component
@Data
public class RsaConfig {
@Value("${keysUrl.pubKeyFileUrl}")
private String pubKeyFileUrl;
@Value("${keysUrl.priKeyFileUrl}")
private String priKeyFileUrl;
@Value("${keysUrl.secret}")
private String secret;
/*
* Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
* */
@PostConstruct //构造方法以后执行
private void init(){
File pubfile=new File(this.getPubKeyFileUrl());//创建公钥的文件类
File prifile=new File(this.getPriKeyFileUrl());//创建私钥的文件类
//公钥或者私钥不存在,新建一套
if (!pubfile.exists() || !prifile.exists())
{
try {
RsaUtil.generateKey(this.getPubKeyFileUrl(), this.getPriKeyFileUrl(), this.getSecret());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Result 和 User 实体类
响应数据的封装
import java.util.HashMap;
import java.util.Map;
public class Result {
private Map<String, Object> data = new HashMap<>();
//添加返回数据
public Result add(String key, Object object) {
this.getData().put(key, object);
return this;
}
public Map<String, Object> getData() {
return data;
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class User {
private String username;
}
ResponseEncrypt 自定义注解
用于标记哪个响应接口方法需要加密
import java.lang.annotation.*;
/**
* 进行响应参数加密
* @author mr.xiao
* @date 2021/3/28
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseEncrypt {
}
DecryptRequestBodyAdvice 请求参数拦截类
用于请求参数的解密
import com.alibaba.fastjson.JSONObject;
import com.xiao.springbootencrypt.util.Base64Util;
import com.xiao.springbootencrypt.util.RsaUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.stereotype.Component;
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;
import java.util.Map;
/*
* 请求参数的解密操作
* RequestBodyAdvice可以理解为在@RequestBody之前需要进行的操作
* */
@Component
@ControllerAdvice(basePackages = "com.xiao.springbootencrypt.controller")
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
@Autowired
RsaUtil rsaUtil;
/*
* supports 定义什么注解生效,默认 @RequestBody
* */
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
return httpInputMessage;
}
@Override
public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
String dealData = null;
Map<String,String> dataMap = (Map)o;
String srcData = dataMap.get("data");
dealData = new String(rsaUtil.decryptData(srcData.getBytes()));
// 字符串转对象
JSONObject jsonObject = JSONObject.parseObject(dealData);
// 返回对象数据
return jsonObject;
}
@Override
public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return null;
}
}
EncryResponseBodyAdvice 响应参数加密拦截类
用于响应数据的加密
import com.alibaba.fastjson.JSON;
import com.xiao.springbootencrypt.annotation.ResponseEncrypt;
import com.xiao.springbootencrypt.bean.Result;
import com.xiao.springbootencrypt.util.Base64Util;
import com.xiao.springbootencrypt.util.RsaUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/*
* 响应参数加密参数
* ResponseBodyAdvice可以理解为在@ResponseBody之后进行的操作,所以当接口需要加解密时,
* 在使用@RequestBody接收前台参数之前可以先在RequestBodyAdvice的实现类中进行参数的解密,
* 当操作结束需要返回数据时,可以在@ResponseBody之后进入ResponseBodyAdvice的实现类中进行参数的加密。
* */
@Component
@ControllerAdvice(basePackages = "com.xiao.springbootencrypt.controller")
@Slf4j
public class EncryResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Autowired
RsaUtil rsaUtil;
/*
* supports 定义什么注解生效,默认 @ResponseBody
* */
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
// 设置带有 ResponseEncrypt 注解的才做加密.
return methodParameter.hasMethodAnnotation(ResponseEncrypt.class);
}
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
//通过 ServerHttpRequest的实现类ServletServerHttpRequest 获得HttpServletRequest
ServletServerHttpRequest sshr = (ServletServerHttpRequest) serverHttpRequest;
//此处获取到request 是为了取到在拦截器里面设置的一个对象 是我项目需要,可以忽略
HttpServletRequest request = sshr.getServletRequest();
byte[] enData = null;
try {
//添加 encry header,告诉前端数据已加密
serverHttpResponse.getHeaders().add("encry", "true");
// 类型转换并获取响应的数据
Result resultData = (Result)o;
Map<String, Object> data = resultData.getData();
// 转成字符串进行加密
String srcDataStr = JSON.toJSONString(data);
enData = rsaUtil.encryptData(srcDataStr.getBytes());
log.info("接口={},原始数据={},加密后数据={}", request.getRequestURI(), srcDataStr, enData);
} catch (Exception e) {
log.error("异常!", e);
}
// 数据再封装一下
Map<String, String> dataMap = new HashMap<>();
dataMap.put("data",new String(enData));
return dataMap;
}
}
TestController 测试类
import com.xiao.springbootencrypt.annotation.ResponseEncrypt;
import com.xiao.springbootencrypt.bean.Result;
import com.xiao.springbootencrypt.bean.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.ArrayList;
@RestController
public class TestController {
Logger log = LoggerFactory.getLogger(getClass());
/**
* 响应数据 加密
*/
@RequestMapping(value = "/sendResponseEncryData")
// 响应加密
@ResponseEncrypt
public Result sendResponseEncryData() {
ArrayList<User> userList = new ArrayList<>();
userList.add(new User("Mr.xiao"));
return new Result().add("userInfo",userList);
}
/**
* 解密请求参数,并返回解密后的数据
*/
@RequestMapping(value = "/getRequestData")
// 当方法参数里面有HttpServeltResponse 时它将不会生效, 使用response返回数据
public Result getRequestData( @RequestBody Object object) throws IOException, NoSuchFieldException {
log.info("controller接收的参数object={}", object.toString());
// 本来不能再用 @ResponseBody , 否则响应数据会加密,但是不加 @ResponseBody会运行时报错。
// 使用自定义注解就可以不以@ResponseBody为标准。
return new Result().add("DecryptData", object);
}
}
4.4、测试
访问响应数据加密接口,查看结果:
后台打印结果:
访问请求数据解密接口,查看结果:
后台打印结果:
到此 SpringBoot 基于RequestBodyAdvice 和 ResponseBodyAdvice 实现数据的加/解密就已大功告成啦 ~ ~