客户端、服务端网络通信,为了安全,会对报文数据进行加解密操作。
在SpringBoot项目中,最好使用参考AOP思想,加解密与Controller业务逻辑解耦,互不影响。
以解密为例:需要在request请求到达Controller之前进行拦截,获取请求body中的密文并对其进行解密,然后把解密后的明文重新设置到request的body上。
拦截器、过滤器、Controller之间的关系

如图所示,在Request对象进入Controller之前,可使用Filter过滤器和Interceptor拦截器进行拦截。
过滤器、拦截器主要差异:
1、过滤器来自于 Servlet;而拦截器来自于 Spring 框架; 2、触发时机不同:请求的执行顺序是:请求进入容器 > 进入过滤器 > 进入 Servlet > 进入拦截器 > 执行控制器(Controller) 3、过滤器是基于方法回调实现的;拦截器是基于动态代理(底层是反射)实现的; 4、过滤器是 Servlet 规范中定义的,所以过滤器要依赖 Servlet 容器,它只能用在 Web 项目中;拦截器是 Spring 中的一个组件,因此拦截器既可以用在 Web 项目中,同时还可以用在 Application 或 Swing 程序中; 5、过滤器通常是用来实现通用功能过滤的,比如:敏感词过滤、字符集编码设置、响应数据压缩等功能;拦截器更接近业务系统,所以拦截器主要用来实现项目中的业务判断的,比如:登录判断、权限判断、日志记录等业务;
对于我们当前应用场景来说,区别就是过滤器更适用于修改request body。
具体实现分析
修改请求,会有两个问题:
1、请求体的输入流被读取,它就不能再被其他组件读取,因为输入流只能被标记、重置,并且在读取后会被消耗。
2、HttpServletRequest对象的body数据只能get,不能set,不能再次赋值。而咱们的需求是需要给HttpServletRequest body解密并重新赋值。
基于以上两个问题,咱们需要定义一个HttpServletRequest实现类,增加赋值方法,来满足我们的需求。
CustomHttpServletRequestWrapper.java
import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; /** * 自定义HttpServletRequestWrapper * qxc * 20240622 */ public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper { private String body; public CustomHttpServletRequestWrapper(HttpServletRequest request) { super(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(); } } } body = stringBuilder.toString(); } @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 setBody(String body) { this.body = body; } }
接下来,继续写Filter类,用于拦截请求,解析body, 解密报文,替换请求body数据。
RequestWrapperFilter.java
import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Map; import java.util.Objects; /** * 自定义Filter * qxc * 20240622 */ @Slf4j public class RequestWrapperFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { CustomHttpServletRequestWrapper customHttpServletRequestWrapper = null; try { HttpServletRequest req = (HttpServletRequest) request; customHttpServletRequestWrapper = new CustomHttpServletRequestWrapper(req); preHandle(customHttpServletRequestWrapper); } catch (Exception e) { log.warn("customHttpServletRequestWrapper Error:", e); } chain.doFilter((Objects.isNull(customHttpServletRequestWrapper) ? request : customHttpServletRequestWrapper), response); } public void preHandle(CustomHttpServletRequestWrapper request) throws Exception { //仅当请求方法为POST时修改请求体 if (!request.getMethod().equalsIgnoreCase("POST")) { return; } //读取原始请求体 StringBuilder originalBody = new StringBuilder(); String line; try (BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()))) { while ((line = reader.readLine()) != null) { originalBody.append(line); } } String bodyText = originalBody.toString(); //json字符串转成map集合 Map<String, String> map = getMap(bodyText); //获取解密参数,解密数据 if (map != null && map.containsKey("time") && map.containsKey("data")) { String time = map.get("time"); String key = "基于时间戳等参数生成密钥、此处请换成自己的密钥"; String data = map.get("data"); //解密数据 String decryptedData = Cipher.decrypt(key, data); //为请求对象重新设置body request.setBody(decryptedData); } } private Map<String, String> getMap(String text) { ObjectMapper objectMapper = new ObjectMapper(); try { // 将JSON字符串转换为Map Map<String, String> map = objectMapper.readValue(text, Map.class); return map; } catch (IOException e) { e.printStackTrace(); return null; } } }
AES加解密算法封装
Cipher.java
import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Base64; import javax.crypto.spec.SecretKeySpec; /** * 自定义AES加解密算法类 * qxc * 20240622 */ public class Cipher { public static String encrypt(String key, String text){ try { SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES"); cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKeySpec); byte[] encryptedBytes = cipher.doFinal(text.getBytes("UTF-8")); String cipherText = base64Encode(encryptedBytes); return cipherText; }catch (Exception ex){ ex.printStackTrace(); return ""; } } public static String decrypt(String key, String cipherText) { try { SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES"); cipher.init(javax.crypto.Cipher.DECRYPT_MODE, secretKeySpec); byte[] decryptedBytes = cipher.doFinal(base64Decode(cipherText)); return new String(decryptedBytes, StandardCharsets.UTF_8); }catch (Exception ex){ ex.printStackTrace(); return ""; } } public static String getMd5(String input) { try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(input.getBytes()); byte[] digest = md.digest(); BigInteger bigInt = new BigInteger(1, digest); String md5Hex = bigInt.toString(16); while (md5Hex.length() < 32) { md5Hex = "0" + md5Hex; } return md5Hex; } catch (Exception e) { e.printStackTrace(); return ""; } } public static String base64Encode(byte[] bytes) { if (bytes != null && bytes.length > 0) { return Base64.getEncoder().encodeToString(bytes); } return ""; } public static byte[] base64Decode(String base64Str) { if (base64Str != null && base64Str.length() > 0) { return Base64.getDecoder().decode(base64Str); } return new byte[]{}; } }
最后,需要在WebMvcConfigurer中配置并使用RequestWrapperFilter
import com.qxc.server.encryption.RequestWrapperFilter; import com.qxc.server.jwt.JwtInterceptor; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.core.Ordered; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @SpringBootApplication public class ServerApplication implements WebMvcConfigurer { public static void main(String[] args) { SpringApplication.run(ServerApplication.class, args); } @Bean public FilterRegistrationBean servletRegistrationBean() { RequestWrapperFilter userInfoFilter = new RequestWrapperFilter(); FilterRegistrationBean<RequestWrapperFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(userInfoFilter); bean.setName("requestFilter"); bean.addUrlPatterns("/*"); bean.setOrder(Ordered.LOWEST_PRECEDENCE); return bean; } @Override public void addInterceptors(InterceptorRegistry registry) { } }
这样,就实现了请求报文的解密、数据替换,而且对Controller逻辑没有影响。