背景:客户端将加密的数据通过post请求发送到服务端,服务端修改request里的body,将解密后的body给到controller,服务器端请求处理后,统一在响应拦截处将加密后的数据返回给客户端
?1、加解密方法
package com.hi.hailiaowenan.base.utils;
import org.springframework.util.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
// CryptoJS前端加密后台Java解密
public class DesCipherUtil {
private static String decryptKey = "aaaabbbbccccdddd";
private static String encryptKey = "aaaabbbbccccdddd";
/**
* AES解密
* @param encryptStr 密文
* @param decryptKey 秘钥,必须为16个字符组成
* @return 明文
* @throws Exception
*/
public static String aesDecrypt(String encryptStr) throws Exception {
if (StringUtils.isEmpty(encryptStr) || StringUtils.isEmpty(decryptKey)) {
return null;
}
byte[] encryptByte = Base64.getDecoder().decode(encryptStr);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES"));
byte[] decryptBytes = cipher.doFinal(encryptByte);
return new String(decryptBytes);
}
/**
* AES加密
* @param content 明文
* @param encryptKey 秘钥,必须为16个字符组成
* @return 密文
* @throws Exception
*/
public static String aesEncrypt(String content) throws Exception {
if (StringUtils.isEmpty(content) || StringUtils.isEmpty(encryptKey)) {
return null;
}
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));
byte[] encryptStr = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptStr);
}
// 测试加密与解密
// public static void main(String[] args) {
// try {
// String secretKey = "aaaabbbbccccdddd";
// String content = "{ a: 1, b: 2}";
// String s1 = aesEncrypt(content, secretKey);
// System.out.println(s1);
// String s = aesDecrypt(s1, secretKey);
// System.out.println(s);
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
}
2、通过自定义过滤器,实现controller层的接口解密,并将流继续保存下去
package com.hi.hailiaowenan.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSONObject;
import com.hi.hailiaowenan.base.bean.ReturnCode;
import com.hi.hailiaowenan.base.exception.UserException;
import com.hi.hailiaowenan.base.utils.DesCipherUtil;
import com.hi.hailiaowenan.wrapper.RequestWrapper;
import com.hi.hailiaowenan.wrapper.ResponseWrapper;
import org.apache.commons.lang.StringUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Map;
@WebFilter(urlPatterns = "/*",filterName = "channelFilter")
// 通过自定义过滤器,将流继续保存下去 https://www.codenong.com/cs107098749/
public class ChannelFilter implements Filter {
// 项目启动初始化的时候会被加载。
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
// 过滤请求,预处理。
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// filterChain.doFilter(servletRequest, servletResponse);
// return;
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
ResponseWrapper responseWrapper = null;
String desnet = httpServletRequest.getHeader("Desnet"); // 是否需要加密
String allparams = getBody(httpServletRequest); // 请求body
httpServletRequest = new RequestWrapper(httpServletRequest, allparams);
//判断body有值 且 需要加密 且 post请求
if (StringUtils.isNotEmpty(desnet)) {
if ("POST".equals(httpServletRequest.getMethod().toUpperCase())) {
// 获取httpServletRequest的body参数
if (StringUtils.isNotEmpty(allparams)) {
Map<String, Object> allparamsMap = JSONObject.parseObject(allparams);
Object aesparams = allparamsMap.get("params");
if (aesparams != null) {
String desparams = "";
try {
desparams = DesCipherUtil.aesDecrypt(aesparams.toString());
} catch (Exception e) {
throw new UserException(ReturnCode.BAD_REQUEST, "解密出错");
}
JSONObject jsStr = JSONObject.parseObject(desparams);
String ts = jsStr.toJSONString();
// 将参数放入重写的方法中
httpServletRequest = new RequestWrapper(httpServletRequest, ts);
responseWrapper = new ResponseWrapper(httpServletResponse);
filterChain.doFilter(httpServletRequest, responseWrapper); // 执行过滤
}else{
filterChain.doFilter(httpServletRequest, servletResponse); // 执行过滤
}
}else{
filterChain.doFilter(httpServletRequest, servletResponse); // 执行过滤
}
} else{
filterChain.doFilter(httpServletRequest, servletResponse); // 执行过滤
}
}else{
filterChain.doFilter(httpServletRequest, servletResponse); // 执行过滤
}
//判断是否有值 且 需要加密 且 post请求
if(responseWrapper != null){
byte[] contentByte = responseWrapper.getContent(); // 获取返回值
if (contentByte.length > 0 && StringUtils.isNotEmpty(desnet) && "POST".equals(httpServletRequest.getMethod().toUpperCase())) {
String result = new String(contentByte, "UTF-8");
String encryptStr = "";
try {
encryptStr = DesCipherUtil.aesEncrypt(result); // 加密
} catch (Exception e) {
throw new UserException(ReturnCode.BAD_REQUEST, "加密出错");
}
// 把返回值输出到客户端 这里用的 servletResponse,比较特殊
ServletOutputStream out = servletResponse.getOutputStream();
out.write(encryptStr.getBytes());
out.flush();
}
}
// filterChain.doFilter(request, responseWrapper); // 执行过滤
}
//获取Request的body数据
private String getBody(ServletRequest request) {
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
InputStream inputStream = null;
try {
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);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return stringBuilder.toString();
}
/**
* 哪些路径不处理
* @param request
* @param strArr
* @return
*/
public boolean isIgnore(HttpServletRequest request,String[] strArr) {
String path = request.getRequestURI();
for(String ignore:strArr) {
if(path.contains(ignore)) {
return true;
}
}
return false;
}
// 项目停止前,会执行该方法。
@Override
public void destroy() {
}
}
3、?重写HttpServletRequestWrapper方法,由于HttpServletRequest中的body数据只能get不能set,即不能重新赋值,并且只能读取一次,但是我们的场景却是需要对body重新赋值,这个时候需要我们想办法重写
package com.hi.hailiaowenan.wrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
// 重写HttpServletRequestWrapper方法
public class RequestWrapper extends HttpServletRequestWrapper {
private String body;
public RequestWrapper(HttpServletRequest request, String context) {
super(request);
body = context;
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
return servletInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public String getBody() {
return this.body;
}
public void setBodyJsonStr(String bodyJsonStr) {
this.body = bodyJsonStr;
}
}
4、?重写HttpServletResponseWrapper?方法
package com.hi.hailiaowenan.wrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
// 重写HttpServletResponseWrapper方法
public class ResponseWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream buffer;
private ServletOutputStream out;
public ResponseWrapper(HttpServletResponse httpServletResponse) {
super(httpServletResponse);
buffer = new ByteArrayOutputStream();
out = new WrapperOutputStream(buffer);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return out;
}
@Override
public void flushBuffer() throws IOException {
if (out != null) {
out.flush();
}
}
public byte[] getContent() throws IOException {
flushBuffer();
return buffer.toByteArray();
}
private static class WrapperOutputStream extends ServletOutputStream {
private ByteArrayOutputStream bos;
WrapperOutputStream(ByteArrayOutputStream bos) {
this.bos = bos;
}
@Override
public void write(int b)
throws IOException {
bos.write(b);
}
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setWriteListener(WriteListener arg0) {
// TODO Auto-generated method stub
}
}
}
4、在启动类中注入
@SpringBootApplication
@ServletComponentScan //注册过滤器注解 对应filter/ChannelFiliter
完整代码?
package com.hi.hailiaowenan;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.hi.hailiaowenan.base.config.AliConfig;
import com.hi.hailiaowenan.base.config.QqConfig;
import com.hi.hailiaowenan.base.config.WxConfig;
import com.hi.hailiaowenan.base.config.ToutiaoConfig;
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@EnableConfigurationProperties({WxConfig.class, AliConfig.class, ToutiaoConfig.class, QqConfig.class})
@ComponentScan(basePackages = "com.hi.hailiaowenan.**")
@MapperScan("com.hi.hailiaowenan.**.**.mapper")
@SpringBootApplication
@ServletComponentScan //注册过滤器注解 对应filter/ChannelFiliter
public class HailiaowenanApplication {
public static void main(String[] args) {
SpringApplication.run(HailiaowenanApplication.class, args);
}
// redis 乱码序列化、
@Bean(name = "redisTemplate")
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
至此,完成了客户端加密,服务器端解密参数,并将加密后的数据返回给客户端
参考文章:
SpringBoot通过拦截器获取请求参数和返回结果进行操作 | 码农家园
CryptoJS前端加密后台Java解密 - 简书
springboot过滤器中将form表单和body(json)形式的进参拦截修改里面参数内容(重写HttpServletRequestWrapper里边的方法)_酸菜鱼(szy)的博客-CSDN博客_formbody json
重写Request,修改Request中的Body内容 - 简书
SpringBoot实现数据加密传输_酷酷小饼干的博客-CSDN博客_springboot 加密传输???????
|