使用场景
前端发送过来的数据是经过加密的数据,我这里前端发送过来的数据格式如下所示:
{
"id":"7f9JPPdYpppQZSwB7QBSGQ==",
"name":"7f9JPPdYpppQZSwB7QBSGQhadkSjhaf=="
}
因为每一个API接口的请求数据是不一样,如果直接在控制器进行解密那么就会产生大量的冗余代码,这是我们不希望看到的,最好的办法就是使用Springboot的拦截器在请求到达控制器之前将加密的数据进行解密,再继续发送给控制器,之后控制器就能愉快的处理解密完成的数据了。
需求分析
因为 json 数据是属于请求体,只能通过读取 request 流进行获取加密前的数据,但是流只能读取一次,如果在拦截器读取流之后,后续的控制器就无法再次读取流,导致拿不到数据,所以这里我们需要使用 HttpServletRequestWrapper 将流进行拷贝,实现流的重复读取。
下面直接给出各部分相应的代码:
- 过滤器
package com.yiyun.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Slf4j
@WebFilter(filterName = "myFilter", urlPatterns = {"/**"})
@Component
public class GlobalFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//上传文件不做解密处理
if ("multipart/form-data".equals(((HttpServletRequest) request).getHeader("Content-Type"))) {
chain.doFilter(request, response);
} else {
MyHttpServletRequestWrapper MyHttpServletRequest = new MyHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(MyHttpServletRequest, response);
}
}
}
注意:此处做了一个判断,如果是上传文件就不需要对参数进行解密,直接把原本的request继续往下传递就可以,不然上传文件的流会被损害,导致无法上传文件。
- 拦截器
package com.yiyun.interceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yiyun.filter.MyHttpServletRequestWrapper;
import com.yiyun.utils.AesUtil;
import com.yiyun.utils.ThreadLocalUtil;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.Map;
/**
* @author 创梦流浪人
* @version 1.0
*/
public class GlobalInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.判断sign timestamp参数是否携带 app_id不用在这里判断,直接在相关的bean上面通过注解实现
checkMustParameter(request);
//2.根据URI 判断是否需要鉴权,比如文章设置了 VIP 可见、登录可见、游客可见等,
// 那么如果是查询文章的 URI 就要做一个鉴权判断
//3.参数解密
decryptParameters(request);
ThreadLocalUtil.set("你好");
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
ThreadLocalUtil.remove();
}
/**
* 检查每个请求必须携带的参数,并且校验是否合法
*/
private void checkMustParameter(HttpServletRequest request) {
String sign = request.getParameter("sign");
String timestamp = request.getParameter("timestamp");
if (!StringUtils.hasText(sign) || !StringUtils.hasText(timestamp)) {
throw new RuntimeException("签名获取时间戳参数缺少");
}
}
/**
* 解密参数
*/
private void decryptParameters(HttpServletRequest request) throws IOException {
ServletInputStream inputStream = request.getInputStream();
DataInputStream dataInputStream = new DataInputStream(inputStream);
byte[] bytes = dataInputStream.readAllBytes();
ObjectMapper objectMapper = new ObjectMapper();
Map<String, String> map = objectMapper.readValue(new String(bytes), Map.class);
map.forEach((key, value) -> {
try {
map.put(key, AesUtil.decrypt(value));
} catch (Exception e) {
throw new RuntimeException("解密失败"+e);
}
});
String s = objectMapper.writeValueAsString(map);
((MyHttpServletRequestWrapper) request).setBody(s.getBytes());
}
}
注意:解密参数之后 ((MyHttpServletRequestWrapper) request).setBody(s.getBytes())
将解密后的参数覆盖之前的流。
- HttpServletRequestWrapper
package com.yiyun.filter;
import cn.hutool.core.io.IoUtil;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
private byte[] body;
public MyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
request.setCharacterEncoding("UTF-8");
body = IoUtil.readBytes(request.getInputStream());
}
public void setBody(byte[] body){
this.body = body;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bis = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bis.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
- AES加密解密工具类
package com.yiyun.utils;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class AesUtil {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
private static final byte[] KEY = "1234567891234567".getBytes(StandardCharsets.UTF_8); // 16 bytes key for AES
public static String encrypt(String data) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(KEY, ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
}
public static String decrypt(String encryptedData) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(KEY, ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
return new String(decrypted, StandardCharsets.UTF_8);
}
public static void main(String[] args) {
try {
String originalText = "121454";
System.out.println("Original Text: " + originalText);
String encryptedText = encrypt(originalText);
System.out.println("Encrypted Text: " + encryptedText);
String decryptedText = decrypt(encryptedText);
System.out.println("Decrypted Text: " + decryptedText);
} catch (Exception e) {
e.printStackTrace();
}
}
}