近期在项目中因为安全红线要求需要进行接口加解密,在此记录一下。
这里我是使用AES加解密+过滤器方式实现。
废话不多说,直接上代码。
1.创建AES加解密工具类。(此处也可以加上aes的偏移量做进一步的安全性加密)
public class AESUtil {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
public static String encrypt(String plaintext, String secretKey) throws Exception {
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public static String decrypt(String ciphertext, String secretKey) throws Exception {
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] encryptedBytes = Base64.getDecoder().decode(ciphertext);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
}
2.创建请求参数解密包装器。(如果前端请求入参没有加密的前提下,可以不用使用此方法。)
@Slf4j
public class ParameterTrimWrapper extends HttpServletRequestWrapper {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
private final String ENCRYPTION_KEY = "***************";
//用于存放post请求体数据
private byte[] body;
private Map<String, String[]> params = new HashMap<>();
private static final String POST = "POST";
private static final String GET = "GET";
public ParameterTrimWrapper(HttpServletRequest request) throws IOException {
super(request);
String method = request.getMethod();
//处理post请求 对请求体进行情况
if (POST.equalsIgnoreCase(method)) {
//getInputStream()读取请求体数据流
String bodyJson = new String(IOUtils.toByteArray(super.getInputStream()), StandardCharsets.UTF_8);
if (StringUtils.isNotEmpty(bodyJson)) {
try {
log.info("入参解密前: {}", bodyJson);
bodyJson = AESUtil.decrypt(bodyJson, ENCRYPTION_KEY);
log.info("入参解密后: {}", bodyJson);
} catch (Exception e) {
e.printStackTrace();
}
this.body = bodyJson.getBytes(StandardCharsets.UTF_8);
}
} else if (GET.equalsIgnoreCase(method)) {
Map<String, Object> map = new HashMap<>();
Map<String, String[]> parameterMap = new HashMap<>(request.getParameterMap());
if (MapUtil.isEmpty(parameterMap)) {
return;
}
String[] params = parameterMap.get("params");
if (ObjectUtil.isEmpty(params)) {
return;
}
String parameter = String.valueOf(params[0]);
if (StrUtil.isEmpty(parameter)) {
return;
}
log.info("get入参解密前: {}", parameter);
// 对加密参数进行解密
try {
String decryptedParam = AESUtil.decrypt(parameter, ENCRYPTION_KEY);
map = JSONUtil.toBean(decryptedParam, Map.class);
log.info("get入参解密后: {}", decryptedParam);
} catch (Exception e) {
e.printStackTrace();
}
map.forEach((key, value) -> {
if (value instanceof String[]) {
this.params.put(key, (String[]) value);
} else if (value instanceof String) {
this.params.put(key, new String[]{(String) value});
} else if (value == null) {
this.params.put(key, null);
} else {
this.params.put(key, new String[]{String.valueOf(value)});
}
});
this.params.putAll(request.getParameterMap());
log.info(request.getParameterMap().toString());
}
}
public String getBody() {
return new String(this.body, StandardCharsets.UTF_8);
}
@Override
public String[] getParameterValues(String name) {
return params.get(name);
}
@Override
public Map<String, String[]> getParameterMap() {
return this.params;
}
@Override
public String getParameter(String name) {
String[] values = params.get(name);
if (values == null || values.length == 0) {
return null;
}
return values[0];
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
//重写getInputStream(),最后程序回来调用这个方法
@Override
public ServletInputStream getInputStream() throws IOException {
//bais处理过后的最新数据流
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
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 bais.read();
}
};
}
}
3.自定义加密过滤器。
// 自定义加密过滤器
@Slf4j
public class EncryptionFilter implements Filter {
// AES加密密钥
private final String ENCRYPTION_KEY = "*******************";
// 加密接口列表
private final Set<String> WHITELIST = new HashSet<>(Arrays.asList(
"/sso/getUserInfo",
// 添加其他加密响应数据接口...
));
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (isWhitelisted(request.getRequestURI())) {
// 创建自定义的响应包装器(此处前端请求入参进行加密,需进行参数解密。)
EncryptionResponseWrapper responseWrapper = new EncryptionResponseWrapper(response);
log.info("进入请求参数解密: {}", request.getRequestURI());
ParameterTrimWrapper parameterTrimWrapper = new ParameterTrimWrapper(request);
log.info("参数解密处理完成");
filterChain.doFilter(parameterTrimWrapper, responseWrapper);
// 对私密信息进行加密处理
String responseData = responseWrapper.getResponseData();
String encryptedData = null;
try {
encryptedData = AESUtil.encrypt(responseData, ENCRYPTION_KEY);
log.info("加密后的结果:{}", encryptedData);
} catch (Exception e) {
e.printStackTrace();
}
try {
String deRes = AESUtil.decrypt(encryptedData, ENCRYPTION_KEY);
log.info("解密后的结果:{}", deRes);
} catch (Exception e) {
e.printStackTrace();
}
//获取加密数据字节数组
byte[] encryptedBytes = encryptedData.getBytes(StandardCharsets.UTF_8);
response.setContentType("application/json");
response.setContentLength(encryptedBytes.length);
try {
// 更新响应内容为加密后的数据
response.getOutputStream().write(encryptedBytes);
log.info("加密数据响应到到客户端:{}", encryptedData);
// 刷新流确保数据立即发送到客户端
response.getOutputStream().flush();
} catch (IOException e) {
e.printStackTrace();
}
} else {
// 对其他请求进行正常处理
log.info("非加密接口直接处理: {}", request.getRequestURI());
filterChain.doFilter(request, response);
return;
}
}
// 判断请求路径是否在加密接口列表中
private boolean isWhitelisted(String requestURI) {
//使用共同前缀提高白名单匹配的效率
return WHITELIST.stream().anyMatch(requestURI::startsWith);
}
// 自定义加密后的请求包装器
public class EncryptionResponseWrapper extends HttpServletResponseWrapper {
private final ByteArrayOutputStream outputStream;
private final ServletOutputStream servletOutputStream;
public EncryptionResponseWrapper(HttpServletResponse response) {
super(response);
outputStream = new ByteArrayOutputStream();
servletOutputStream = new ServletOutputStream() {
@Override
public void write(int b) throws IOException {
outputStream.write(b);
}
@Override
public boolean isReady() {
// 根据实际需求返回相应的值
return true;
}
@Override
public void setWriteListener(WriteListener writeListener) {
// No-op
}
};
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return servletOutputStream;
}
@Override
public void flushBuffer() throws IOException {
servletOutputStream.flush();
}
public String getResponseData() {
return new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
}
}
}
4.启动类,把加密过滤器bean注册到spring中。
@SpringBootApplication
public class BootApplication {
public static void main(String[] args) {
SpringApplication.run(BootApplication.class, args);
}
// 配置加密过滤器
@Bean
public FilterRegistrationBean<EncryptionFilter> encryptionFilter() {
FilterRegistrationBean<EncryptionFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new EncryptionFilter());
//配置拦截所有请求地址(此处可以将项目接口路径前缀写入)
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
5.流程,经过以上代码写入之后,前端请求入参加密,后台将加密过滤器注册到spring中,所有请求接口路径在你配置的whiteList中,都将在ParameterTrimWrapper包装器里区分get与post请求进行aes解密,再去进行接口调用,访问数据库查询数据。然后对返回数据进行aes加密到前端,前端解密并在页面渲染展示数据。
6.结尾slogan:生活每天不一样,你要积极又向上!下次见。