菜鸟笔记
提升您的技术认知

接口入参与返回数据AES加解密

前言

  • 出于项目安全方面的考虑,对接口的入参和返回数据进行加解密,综合考虑效率和安全性总重采用AES的对称加密方式(前后台数据传输时采用base64编码否则会出现乱码现象)废话不多说 直接上代码

请求的拦截器类

注意这里面的AesRequestWrapper类,这个类重写获取流的方法 因为request中的流只能被读取一次,在解密的时候被你读取的话再往应用层传就会报错

import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @Description base64的转换拦截器类 拦截后台的请求进行数据的加解密操作
 * @author liang
 */
@Component
public class AesFilter implements Filter {
  


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
  

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  
        AesRequestWrapper requestWrapper = null;
        if (request instanceof HttpServletRequest) {
  
            //把从request读到的流放入body字符串中(做了个base64的转换)
            // 在重写request的getInputStream中的read方法  每次读取request的流实际相当于读取body字符串的byte
            requestWrapper = new AesRequestWrapper((HttpServletRequest) request);
        }
        if (requestWrapper != null ){
  
            // 在chain.doFiler方法中传递新的request对象
            chain.doFilter(requestWrapper, response);
        }else {
  
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {
  

    }
}


AesRequestWrapper重写读取流的类

注意:这里的Base64是jdk1.8的类,他与之前老的base64加解密可能会出现不兼容的问题(让前端也用与jdk1.8匹配的base64加解密方式) AesCoder是我自己写的AES加解密类

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.util.Base64;

/**
 * @author liang
 */
public class AesRequestWrapper extends HttpServletRequestWrapper {
  

    private static final Logger logger = LoggerFactory.getLogger(AesRequestWrapper.class);

    /**
     * 存储请求数据
     */
    private String body;

    public AesRequestWrapper(HttpServletRequest request) {
  
        super(request);
        renewBody(request);
    }

    /**
     * 重写getInputStream方法
     */
    @Override
    public ServletInputStream getInputStream() {
  
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
        return new ServletInputStream() {
  
            @Override
            public boolean isFinished() {
  
                return false;
            }

            @Override
            public boolean isReady() {
  
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
  
            }

            @Override
            public int read() {
  
                return byteArrayInputStream.read();
            }
        };

    }

    /**
     * 重写getReader方法
     */
    @Override
    public BufferedReader getReader() {
  
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    /**
     * 读取body的值
     */
    private void renewBody(HttpServletRequest request) {
  
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        try {
  
            InputStream inputStream = request.getInputStream();
            if (inputStream != null) {
  
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[128];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
  
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            }
            body = new String(AesCoder.decrypt(Base64.getDecoder().decode(stringBuilder.toString())));
        } catch (Exception ex) {
  
            logger.error("报错请求接口的路径:{}",request.getRequestURI());
            logger.error("请求入参aes解密失败:{}",ex);
        } finally {
  
            if (bufferedReader != null) {
  
                try {
  
                    bufferedReader.close();
                } catch (IOException ex) {
  
                    logger.error("请求入参base64转换失败:{}",ex);
                }
            }
        }

    }

    public String getBody() {
  
        return body;
    }

}

AesCoder 加解密类

注意加密私钥密码必须为16位 字母和数字均可

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;

/**
 * aes加解密处理工具类
 * @author liang
 */
public class AesCoder {
  

    /**
     * 加密方式 用AES
     */
    private static final String KEY_ALGORITHM = "AES";

    /**
     * 默认的加密算法
     */
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";

    /**
     * 加密私钥密码
     */
    private static final String PRIVATE_KEY = "wrqiorduis589756";

    static Key key;

    static {
  
        key = toKey(initSecretKey());
    }

    /**
     * 私钥转换为byte数组
     * @return 私钥的byte数组
     */
    private static byte[] initSecretKey() {
  
        return PRIVATE_KEY.getBytes(StandardCharsets.UTF_8);
    }

    private static Key toKey(byte[] key){
  
        //生成密钥
        return new SecretKeySpec(key, KEY_ALGORITHM);
    }

    public static byte[] encrypt(byte[] data) throws Exception{
  
        //实例化
        Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
        //使用密钥初始化,设置为加密模式
        cipher.init(Cipher.ENCRYPT_MODE, key);
        //执行操作
        return cipher.doFinal(data);
    }

    public static byte[] decrypt(byte[] data) throws Exception{
  
        //实例化
        Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
        //使用密钥初始化,设置为解密模式
        cipher.init(Cipher.DECRYPT_MODE, key);
        //执行操作
        return cipher.doFinal(data);
    }

    private static String  showByteArray(byte[] data){
  
        if(null == data){
  
            return null;
        }
        StringBuilder sb = new StringBuilder("{");
        for(byte b:data){
  
            sb.append(b).append(",");
        }
        sb.deleteCharAt(sb.length()-1);
        sb.append("}");
        return sb.toString();
    }


    public static void main(String[] args) throws Exception {
  
        String data ="AES数据阿斯器具";
        System.out.println("加密前数据: string:"+data);
        System.out.println();
        byte[] encryptData = encrypt(data.getBytes());
        String base64 = Base64.getEncoder().encodeToString(encryptData);
        System.out.println("base64 String"+base64);
        System.out.println();
        byte[] base64Data = Base64.getDecoder().decode(base64);
        byte[] decryptData = decrypt(base64Data);
        System.out.println("解密后数据: string:"+new String(decryptData, StandardCharsets.UTF_8));
    }

}

到此为止接口传参的数据解密就完成了 接下来就是返回数据的加密

DealWithResponseBody 全局数据加密类

注意这个注解 @ControllerAdvice 它的作用 传送门
这个ResponseBodyAdvice接口的作用是重写beforeBodyWrite方法对返回体做一些自定义操作 此处我们可以对数据进行AES加密

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

import java.util.Base64;

/**
 * @author liang
 */
@ControllerAdvice
public class DealWithResponseBody implements ResponseBodyAdvice {
  

    private static final Logger logger = LoggerFactory.getLogger(DealWithResponseBody.class);

    private static final String URL = "/medmanage";

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
  
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
  
        String requestPath = request.getURI().getPath();
        //判断是否 需要处理的接口
        if(requestPath.startsWith(URL) && !requestPath.contains("upload")){
  
            try {
  
                //这里小心点别oom 谁在写循环引用 小心我给你寄刀片
                return Base64.getEncoder().encodeToString(AesCoder.encrypt(JSONObject.toJSONString(body, SerializerFeature.WriteMapNullValue,SerializerFeature.DisableCircularReferenceDetect).getBytes()));
            } catch (Exception e) {
  
                logger.error("返回结果的加密失败:{}",e);
            }
        }
        return body;
    }
}

最终总结

  • spring中的拦截器真的很好用,它体现出来的设计思想就是讲业务代码和逻辑处理分层开,设计模式真的能让人受用终生,它不仅仅体现在编程中,做事学习都有可以借鉴的地方