SpringBoot 基于RequestBodyAdvice 和 ResponseBodyAdvice 实现数据的加/解密(采用 RSA 算法 ),“船新版本”!

一、前言:

数据是企业的第四张名片,企业级开发中少不了数据的加密传输。为了预防请求数据被劫持篡改,一般都会对传输的数据进行加密操作,如果每个接口都由我们自己去手动加密和解密,那么工作量太大而且代码冗余。那么有没有简单的方法,借助 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返回数据。


三、新在哪里:

  1. 采用RSA非对称加密,但由于RSA非对称加密只能加密 < 117 字节的字符串,并不满足需求,所有自写了一个数据分段加/解密的方法 segmentDataPretreatment(int maxLength, byte[] data, Cipher cipher), 实现 > 117 字节的字符串加/解密。

  2. ResponseBodyAdvice 只支持带有 @ResponseBody 注解的方法, RequestBodyAdvice 只支持带有 @RequestBody 注解的方法。 这样有个弊端,就是我并不是想每个响应体都加密,而是只对几个特殊重要的接口进行加密,但数据响应体又离不开 @ResponseBody, 导致响应的数据都给加密了,增加服务器负担。这里我采用自定义注解 @ResponseEncrypt,只有带有我自定义的注解的方法才会被加密,这样就达到想加密就加密哪里啦~

  3. 代码里面还多次使用 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 实现数据的加/解密就已大功告成啦 ~ ~

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m0rta1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值