咨询热线:

0898-08982569

当前位置: 首页 > 育儿知识 > 饮食营养

SpringBoot+Vue 后端输出加密,前端请求统一解密

针对客户对数据交互过程中的加密要求,防止直接的数据爬取,对前后端数据请求时的返回数据进行数据的加密。实用性嘛,也就那样了,代码可直接适配Ruoyi SpringBoot+vue项目,具体加密方式和处理仅供参考!

前端

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);
    }
}

后端java

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 data = (HashMap) 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> aClass) {
        // 只有标识了@DESDecrypt注解的控制器才需要解密
        return methodParameter.hasMethodAnnotation(DESDecrypt.class);
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class> aClass) throws IOException {
        return new HttpInputMessage() {
            @Override
            public InputStream getBody() throws IOException {
                String json = null;
                try {
                    InputStream body = httpInputMessage.getBody();
                    HashMap map = (HashMap) 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> aClass) {
        return o;
    }

    @Override
    public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class> 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);
    }
}