文章目录
前言
- 出于项目安全方面的考虑,对接口的入参和返回数据进行加解密,综合考虑效率和安全性总重采用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中的拦截器真的很好用,它体现出来的设计思想就是讲业务代码和逻辑处理分层开,设计模式真的能让人受用终生,它不仅仅体现在编程中,做事学习都有可以借鉴的地方