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