SpringBoot+Vue back-end output encryption, front-end request unified decryption

In response to the customer's encryption requirements during the data interaction process, to prevent direct data crawling, the data is encrypted for the returned data when the front-end and back-end data requests are requested. Practicality, that’s it, the code can be directly adapted to the Ruoyi SpringBoot+vue project, and the specific encryption method and processing are for reference only!

front end

request.js

import axios from 'axios'
import { Notification, MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'

import DES from './des'

axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: process.env.VUE_APP_BASE_API,
  // 超时
  timeout: 10000
})
// request拦截器
service.interceptors.request.use(config => {
  // console.log(config);
  // 是否需要设置 token
  const isToken = (config.headers || {}).isToken === false
  if (getToken() && !isToken) {
    config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
  }
  // get请求映射params参数
  if (config.method === 'get' && config.params) {
    let url = config.url + '?';
    for (const propName of Object.keys(config.params)) {
      const value = config.params[propName];
      var part = encodeURIComponent(propName) + "=";
      if (value !== null && typeof (value) !== "undefined") {
        if (typeof value === 'object') {
          for (const key of Object.keys(value)) {
            if (value[key] !== null && typeof (value[key]) !== 'undefined') {
              let params = propName + '[' + key + ']';
              let subPart = encodeURIComponent(params) + '=';
              url += subPart + encodeURIComponent(value[key]) + '&';
            }
          }
        } else {
          url += part + encodeURIComponent(value) + "&";
        }
      }
    }
    url = url.slice(0, -1);
    config.params = {};
    config.url = url;
  }
  return config
}, error => {
  console.log(error)
  Promise.reject(error)
})

// 响应拦截器
service.interceptors.response.use(res => {
    // 未设置状态码则默认成功状态
    const code = res.data.code || 200;
    // 获取错误信息
    const msg = errorCode[code] || res.data.msg || errorCode['default']
    if (code === 401) {
      MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
          confirmButtonText: '重新登录',
          cancelButtonText: '取消',
          type: 'warning'
        }
      ).then(() => {
        store.dispatch('LogOut').then(() => {
          location.href = '/index';
        })
      }).catch(() => { });
      return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
    } else if (code === 500) {
      Message({
        message: msg,
        type: 'error'
      })
      return Promise.reject(new Error(msg))
    } else if (code !== 200) {
      Notification.error({
        title: msg
      })
      return Promise.reject('error')
    } else {

      console.log(res.data);

      // DES解密
      if (res.data.result) {
        let decrypt = DES.decryptECB(res.data.result, '947dcfd3-1163-4ad7-b0f9-b68a78434406')
        if(res.data.hasOwnProperty("rows")){
          res.data.rows = JSON.parse(decrypt)
        }else{
          res.data.data = JSON.parse(decrypt)
        }
        delete res.data['result']
      }

      return res.data
    }
  },
  error => {
    console.log('err' + error)
    let { message } = error;
    if (message == "Network Error") {
      message = "后端接口连接异常";
    }
    else if (message.includes("timeout")) {
      message = "系统接口请求超时";
    }
    else if (message.includes("Request failed with status code")) {
      message = "系统接口" + message.substr(message.length - 3) + "异常";
    }
    Message({
      message: message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service

des.js

import CryptoJS from 'crypto-js'

export default {
    // 加密
    encryptECB(text, textKey) {
        //把私钥转换成16进制的字符串
        var key = CryptoJS.enc.Utf8.parse(textKey);
        //模式为ECB padding为Pkcs7
        var encrypted = CryptoJS.DES.encrypt(text, key, {
            mode: CryptoJS.mode.ECB,
            padding: CryptoJS.pad.Pkcs7
        });
        //加密出来是一个16进制的字符串
        return encrypted.ciphertext.toString();

    },

    // 解密
    decryptECB(ciphertext, textKey) {
        //把私钥转换成16进制的字符串
        var key = CryptoJS.enc.Utf8.parse(textKey);
        console.log(123, key);
        // console.log(CryptoJS.enc.Utf8.stringify(key));
        //把需要解密的数据从16进制字符串转换成字符byte数组
        var decrypted = CryptoJS.DES.decrypt({
            ciphertext: CryptoJS.enc.Hex.parse(ciphertext)
        }, key, {
            mode: CryptoJS.mode.ECB,
            padding: CryptoJS.pad.Pkcs7
        });
        //以utf-8的形式输出解密过后内容
        return decrypted.toString(CryptoJS.enc.Utf8);
    }
}

backendjava

package com.silen.framework.aspect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 只有使用了该注解的方法才需要执行AES解密
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DESDecrypt { }
package com.silen.framework.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.silen.common.config.SilenConfig;
import com.silen.common.core.domain.AjaxResult;
import com.silen.common.core.page.TableDataInfo;
import com.silen.framework.aspect.utils.Constant;
import com.silen.framework.aspect.utils.DESUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Controller AES加密:针对所有请求的返回结果进行加密
 */
@Aspect
@Order(2)
@Component
public class DESEncryptAspect {

    @Pointcut("execution(* com.silen.*.controller..*.*(..))")
    public void pointcut() {}

    @Around("pointcut()")
    @SuppressWarnings("unchecked")
    public Object decrypt(ProceedingJoinPoint point) throws Throwable {
        // 获取到控制器返回结果
        Object proceed = point.proceed(point.getArgs());
        if(proceed instanceof AjaxResult){
            AjaxResult r = (AjaxResult) proceed;
            // 如果包含结果集,将结果集进行加密后返回
            if (r.containsKey("data")) {
                Object json = r.get("data");
                String encrypt = DESUtil.encode(Constant.DES_KEY,JSON.toJSONString(json));
                //String encrypt = AESUtil.AESEncrypt(JSON.toJSONString(json));
                r.remove("data");
                r.put(Constant.SUCCESS_NAME, encrypt);
            }
            return r;
        }else if(proceed instanceof TableDataInfo){
            TableDataInfo r =(TableDataInfo)proceed;
                List<?> rows = r.getRows();
            String encrypt = DESUtil.encode(Constant.DES_KEY, JSON.toJSONString(rows));
            r.setRows(new ArrayList<>());
            r.setResult(encrypt);
            return r;
        }
        return proceed;
    }

}
package com.silen.framework.aspect;

import com.alibaba.fastjson.JSON;
import com.silen.framework.aspect.exception.DESException;
import com.silen.framework.aspect.exception.TypeConverterException;
import com.silen.framework.aspect.utils.Constant;
import com.silen.framework.aspect.utils.DESUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.HashMap;

/**
 * Controller AES解密:GET、DELETE请求
 *    由于AESOtherDecryptAspect实现的RequestBodyAdvice只针对拥有请求体的HTTP请求生效
 *    而GET、DELETE请求不包含请求体,所以这里单独使用AOP进行解密
 */
@Aspect
@Order(1)
@Component
public class DESGetDeleteDecryptAspect {

    // 切入点:只有使用了@DESDecrypt注解的GET请求才会执行解密
    @Pointcut("@annotation(com.silen.framework.aspect.DESDecrypt) && " +
            "(@annotation(org.springframework.web.bind.annotation.GetMapping) || @annotation(org.springframework.web.bind.annotation.DeleteMapping)) && " +
            "execution(* com.silen.*.controller..*.*(..))")
    public void pointcut() { }

    @Around("pointcut()")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取到请求的参数列表进行解密
        Object[] args = joinPoint.getArgs();
        this.decrypt(args);
        // 执行将解密的结果交给控制器进行处理,并返回处理结果
        return joinPoint.proceed(args);
    }

    // 解密方法
    @SuppressWarnings("unchecked")
    public void decrypt(Object[] args) throws DESException, TypeConverterException {
        // 获取请求参数并转换为字符串(密文)
        HashMap<String, Object> data = (HashMap<String, Object>) args[0];
        String encrypt = data.get("json").toString();
        // 将密文解密为JSON字符串
        String json = DESUtil.decode(Constant.DES_KEY,encrypt);
        // 将JSON字符串转换为Map集合,并替换原本的参数
        args[0] = JSON.parse(json);
    }

}
package com.silen.framework.aspect;

import com.alibaba.fastjson.JSON;
import com.silen.framework.aspect.utils.Constant;
import com.silen.framework.aspect.utils.DESUtil;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
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.io.InputStream;
import java.lang.reflect.Type;
import java.util.HashMap;

/**
 * Controller AES解密:POST、PUT请求
 *    1. 使用@ControllerAdvice注解扫描Controller所在位置
 *    2. 实现RequestBodyAdvice接口来处理参数
 *
 *    经测试:RequestBodyAdvice执行优先级高于AOP
 */
@ControllerAdvice("com.silen.*.controller")
public class DESPostPutDecryptAspect implements RequestBodyAdvice {

    // 判断当前Controller是否需要进行参数解密
    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        // 只有标识了@DESDecrypt注解的控制器才需要解密
        return methodParameter.hasMethodAnnotation(DESDecrypt.class);
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        return new HttpInputMessage() {
            @Override
            public InputStream getBody() throws IOException {
                String json = null;
                try {
                    InputStream body = httpInputMessage.getBody();
                    HashMap<String, Object> map = (HashMap<String, Object>) JSON.parse(IOUtils.toString(body));
                    String encrypt = map.get("json").toString();
                     json = DESUtil.decode(Constant.DES_KEY,encrypt);
                } catch (Exception e) {
                    // TODO 异常处理待考究
                }
                return IOUtils.toInputStream(json);
            }
            @Override
            public HttpHeaders getHeaders() {
                return httpInputMessage.getHeaders();
            }
        };
    }

    @Override
    public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return o;
    }

    @Override
    public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return o;
    }

}
package com.silen.framework.aspect.exception;

/**
 * AES数据加密解密异常
 */
public class DESException extends Exception {

    public DESException(String msg) {
        super(msg);
    }

}
package com.silen.framework.aspect.exception;

/**
 * 类型转换异常
 */
public class TypeConverterException extends Exception{

    public TypeConverterException(String msg) {
        super(msg);
    }

}
package com.silen.framework.aspect.utils;

import javax.crypto.Cipher;
import java.security.Key;

/**
 * 加密协议工具
 */
public class DESUtil {

    private static String strDefaultKey = "mykey";
    private Cipher encryptCipher = null;
    private Cipher decryptCipher = null;

    public static String byteArr2HexStr(byte[] arrB) throws Exception {
        int iLen = arrB.length;
        StringBuffer sb = new StringBuffer(iLen * 2);
        for (int i = 0; i < iLen; i++) {
            int intTmp = arrB[i];
            while (intTmp < 0) {
                intTmp = intTmp + 256;
            }
            if (intTmp < 16) {
                sb.append("0");
            }
            sb.append(Integer.toString(intTmp, 16));
        }
        return sb.toString();
    }

    public static byte[] hexStr2ByteArr(String strIn) throws Exception {
        byte[] arrB = strIn.getBytes();
        int iLen = arrB.length;

        byte[] arrOut = new byte[iLen / 2];
        for (int i = 0; i < iLen; i = i + 2) {
            String strTmp = new String(arrB, i, 2);
            arrOut[i / 2] = (byte) Integer.parseInt(strTmp, 16);
        }
        return arrOut;
    }

    public DESUtil() throws Exception {
        this(strDefaultKey);
    }

    public DESUtil(String strKey) throws Exception {
        if (strKey == null)
            return;
        java.security.Security.addProvider(new com.sun.crypto.provider.SunJCE());
        Key key = getKey(strKey.getBytes());
        encryptCipher = Cipher.getInstance("DES");
        encryptCipher.init(Cipher.ENCRYPT_MODE, key);

        decryptCipher = Cipher.getInstance("DES");
        decryptCipher.init(Cipher.DECRYPT_MODE, key);
    }

    public void renderKey(String strKey) throws Exception {
        if (strKey == null)
            return;
        java.security.Security.addProvider(new com.sun.crypto.provider.SunJCE());
        Key key = getKey(strKey.getBytes());
        encryptCipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
        encryptCipher.init(Cipher.ENCRYPT_MODE, key);

        decryptCipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
        decryptCipher.init(Cipher.DECRYPT_MODE, key);
    }

    public byte[] encrypt(byte[] arrB) throws Exception {
        return encryptCipher.doFinal(arrB);
    }

    public String encrypt(String strIn) throws Exception {
        return byteArr2HexStr(encrypt(strIn.getBytes()));
    }


    public byte[] decrypt(byte[] arrB) throws Exception {
        return decryptCipher.doFinal(arrB);
    }

    public String decrypt(String strIn) throws Exception {
        return new String(decrypt(hexStr2ByteArr(strIn)));
    }


    private Key getKey(byte[] arrBTmp) throws Exception {
        byte[] arrB = new byte[8];

        for (int i = 0; i < arrBTmp.length && i < arrB.length; i++) {
            arrB[i] = arrBTmp[i];
        }

        Key key = new javax.crypto.spec.SecretKeySpec(arrB, "DES");

        return key;
    }


    /**
     * 加密
     *
     * @param key 密钥
     * @param str 需要加密的字符串
     * @return
     */
    public static String encode(String key, String str) {
        DESUtil des = null;
        try {
            des = new DESUtil(key);
            return des.encrypt(str);
        } catch (Exception ex) {
        }
        return "";
    }

    public static String decode(String key, String str) {
        DESUtil des = null;
        try {
            des = new DESUtil(key);
            return des.decrypt(str);
        } catch (Exception ex) {
        }
        return "";
    }
    public static String decode(String key, String str,String charset) {
        DESUtil des = null;
        try {
            des = new DESUtil(key);
            return des.decrypt(str,charset);
        } catch (Exception ex) {
        }
        return "";
    }
    public String decrypt(String strIn,String charset) throws Exception {
        return new String(decrypt(hexStr2ByteArr(strIn)),charset);
    }
}

Guess you like

Origin blog.csdn.net/qq_38387996/article/details/128938753