SpringBoot+Vue请求及响应数据加密

项目场景:

在前后端交互的过程中,为了保障数据的安全性,往往会对数据进行加密,这篇就记录一下自己实现加解密的方法


实现效果:

提示:仅实现带有请求体的请求的加密,不支持没有请求体的请求,如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();
    }
}

参考:

(1)VUE+SpringBoot实现传输加密

  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值