|
@@ -0,0 +1,250 @@
|
|
|
|
|
+package com.lxq.system.biz.filter;
|
|
|
|
|
+
|
|
|
|
|
+import cn.hutool.crypto.symmetric.AES;
|
|
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
|
+import com.lxq.common.core.exception.AesDecodeException;
|
|
|
|
|
+import com.lxq.common.core.util.R;
|
|
|
|
|
+import com.lxq.system.biz.config.AesConfigProperties;
|
|
|
|
|
+import io.netty.util.CharsetUtil;
|
|
|
|
|
+import jakarta.servlet.*;
|
|
|
|
|
+import jakarta.servlet.http.HttpServletRequest;
|
|
|
|
|
+import jakarta.servlet.http.HttpServletRequestWrapper;
|
|
|
|
|
+import jakarta.servlet.http.HttpServletResponse;
|
|
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.apache.commons.io.IOUtils;
|
|
|
|
|
+import org.springframework.util.AntPathMatcher;
|
|
|
|
|
+import org.springframework.util.PathMatcher;
|
|
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.BufferedReader;
|
|
|
|
|
+import java.io.ByteArrayInputStream;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.io.InputStreamReader;
|
|
|
|
|
+import java.net.URI;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
|
+import java.util.Base64;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.regex.Matcher;
|
|
|
|
|
+import java.util.regex.Pattern;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 请求解密过滤器
|
|
|
|
|
+ *
|
|
|
|
|
+ * @author lixueqiang
|
|
|
|
|
+ * @date 2026/01/09
|
|
|
|
|
+ */
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@RequiredArgsConstructor
|
|
|
|
|
+public class RequestDecoderFilter implements Filter {
|
|
|
|
|
+
|
|
|
|
|
+ private final AES cryptoAes;
|
|
|
|
|
+
|
|
|
|
|
+ private final ObjectMapper objectMapper;
|
|
|
|
|
+
|
|
|
|
|
+ private final AesConfigProperties aesConfigProperties;
|
|
|
|
|
+
|
|
|
|
|
+ private static final PathMatcher PATH_MATCHER = new AntPathMatcher();
|
|
|
|
|
+
|
|
|
|
|
+ private static final Pattern DELETE_PATH_PATTERN = Pattern.compile("(.+/)[^/]+$");
|
|
|
|
|
+
|
|
|
|
|
+ private static final List<String> FILE_TYPES = List.of("multipart/form-data");
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
|
|
|
|
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
|
|
|
|
|
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
|
|
|
|
|
+ String method = httpRequest.getMethod();
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ String requestURI = httpRequest.getServletPath();
|
|
|
|
|
+ //过滤接口不解密
|
|
|
|
|
+ if (!aesConfigProperties.getFilterUrl().isEmpty() && aesConfigProperties.getFilterUrl().contains(requestURI)) {
|
|
|
|
|
+ chain.doFilter(request, response);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ boolean match = aesConfigProperties.getFilterUrl().stream().anyMatch(url -> PATH_MATCHER.match(url, requestURI));
|
|
|
|
|
+ if (match) {
|
|
|
|
|
+ chain.doFilter(request, response);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String contentType = httpRequest.getContentType();
|
|
|
|
|
+ if (StringUtils.hasText(contentType) && FILE_TYPES.stream().anyMatch(contentType::startsWith)) {
|
|
|
|
|
+ // 这是一个文件请求
|
|
|
|
|
+ chain.doFilter(request, response);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ if ("GET".equalsIgnoreCase(method)) {
|
|
|
|
|
+ request = decryptGetRequest(httpRequest);
|
|
|
|
|
+ } else if ("DELETE".equalsIgnoreCase(method)) {
|
|
|
|
|
+ request = decryptDeleteRequest(httpRequest);
|
|
|
|
|
+ } else if ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) {
|
|
|
|
|
+ request = decryptJsonBody(httpRequest);
|
|
|
|
|
+ }
|
|
|
|
|
+ chain.doFilter(request, response);
|
|
|
|
|
+ } catch (AesDecodeException e) {
|
|
|
|
|
+ log.error("AES解密失败: {}", e.getMessage(), e);
|
|
|
|
|
+ handleException("解密失败", httpResponse);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private HttpServletRequest decryptGetRequest(HttpServletRequest request) {
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ String encryptedValue = request.getParameter("value");
|
|
|
|
|
+ if (encryptedValue != null) {
|
|
|
|
|
+ byte[] decode = Base64.getMimeDecoder().decode(encryptedValue);
|
|
|
|
|
+ String decryptedValue = cryptoAes.decryptStr(new String(decode));
|
|
|
|
|
+ request = new ParameterDecodedRequest(request, decryptedValue);
|
|
|
|
|
+ }
|
|
|
|
|
+ return request;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ throw new AesDecodeException(e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private HttpServletRequest decryptDeleteRequest(HttpServletRequest request) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ String requestURI = request.getRequestURI();
|
|
|
|
|
+ String[] pathSegments = requestURI.split("/");
|
|
|
|
|
+ String encryptedValue = pathSegments[pathSegments.length - 1];
|
|
|
|
|
+
|
|
|
|
|
+ byte[] decode = Base64.getMimeDecoder().decode(encryptedValue);
|
|
|
|
|
+ String decryptedValue = cryptoAes.decryptStr(new String(decode));
|
|
|
|
|
+
|
|
|
|
|
+ Matcher matcher = DELETE_PATH_PATTERN.matcher(requestURI);
|
|
|
|
|
+ if (matcher.find()) {
|
|
|
|
|
+ String lastSegment = matcher.group(1);
|
|
|
|
|
+ String newURI = lastSegment + decryptedValue;
|
|
|
|
|
+ request = new PathDecodedRequest(request, newURI, decryptedValue);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+// Matcher matcher = DELETE_PATH_PATTERN.matcher(requestURI);
|
|
|
|
|
+// if (matcher.find()) {
|
|
|
|
|
+// String encryptedValue = matcher.group(2);
|
|
|
|
|
+// String newURI = requestURI.replace("{" + encryptedValue + "}", decryptedValue);
|
|
|
|
|
+// request = new PathDecodedRequest(request, newURI, decryptedValue);
|
|
|
|
|
+// }
|
|
|
|
|
+ return request;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ throw new AesDecodeException(e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private HttpServletRequest decryptJsonBody(HttpServletRequest request) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ byte[] rawData = IOUtils.toByteArray(request.getInputStream());
|
|
|
|
|
+ String encryptedJson = new String(rawData, StandardCharsets.UTF_8);
|
|
|
|
|
+ byte[] decode = Base64.getMimeDecoder().decode(encryptedJson);
|
|
|
|
|
+ String decryptedJson = cryptoAes.decryptStr(new String(decode));
|
|
|
|
|
+ byte[] decryptedBody = decryptedJson.getBytes(StandardCharsets.UTF_8);
|
|
|
|
|
+ return new CachedBodyHttpServletRequestWrapper(request, decryptedBody);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ throw new AesDecodeException(e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static class CachedBodyHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
|
|
|
|
+ private final byte[] cachedBody;
|
|
|
|
|
+
|
|
|
|
|
+ public CachedBodyHttpServletRequestWrapper(HttpServletRequest request, byte[] cachedBody) {
|
|
|
|
|
+ super(request);
|
|
|
|
|
+ this.cachedBody = cachedBody;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public ServletInputStream getInputStream() {
|
|
|
|
|
+ return new CachedBodyServletInputStream(cachedBody);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public BufferedReader getReader() {
|
|
|
|
|
+ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody);
|
|
|
|
|
+ return new BufferedReader(new InputStreamReader(byteArrayInputStream, StandardCharsets.UTF_8));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static class CachedBodyServletInputStream extends ServletInputStream {
|
|
|
|
|
+ private final ByteArrayInputStream inputStream;
|
|
|
|
|
+
|
|
|
|
|
+ public CachedBodyServletInputStream(byte[] cachedBody) {
|
|
|
|
|
+ this.inputStream = new ByteArrayInputStream(cachedBody);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public boolean isFinished() {
|
|
|
|
|
+ return inputStream.available() == 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public boolean isReady() {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void setReadListener(ReadListener listener) {
|
|
|
|
|
+ throw new UnsupportedOperationException();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public int read() {
|
|
|
|
|
+ return inputStream.read();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static class ParameterDecodedRequest extends HttpServletRequestWrapper {
|
|
|
|
|
+ private final String decryptedValue;
|
|
|
|
|
+
|
|
|
|
|
+ public ParameterDecodedRequest(HttpServletRequest request, String decryptedValue) {
|
|
|
|
|
+ super(request);
|
|
|
|
|
+ this.decryptedValue = decryptedValue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public String getParameter(String name) {
|
|
|
|
|
+ if ("value".equals(name)) {
|
|
|
|
|
+ return decryptedValue;
|
|
|
|
|
+ }
|
|
|
|
|
+ return super.getParameter(name);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static class PathDecodedRequest extends HttpServletRequestWrapper {
|
|
|
|
|
+ private final String newURI;
|
|
|
|
|
+ private final String decryptedValue;
|
|
|
|
|
+
|
|
|
|
|
+ public PathDecodedRequest(HttpServletRequest request, String newURI, String decryptedValue) {
|
|
|
|
|
+ super(request);
|
|
|
|
|
+ this.newURI = newURI;
|
|
|
|
|
+ this.decryptedValue = decryptedValue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public String getRequestURI() {
|
|
|
|
|
+ return newURI;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public String getServletPath() {
|
|
|
|
|
+ return newURI;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public String getParameter(String name) {
|
|
|
|
|
+ if ("value".equals(name)) {
|
|
|
|
|
+ return decryptedValue;
|
|
|
|
|
+ }
|
|
|
|
|
+ return super.getParameter(name);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void handleException(String ex, HttpServletResponse response) throws IOException {
|
|
|
|
|
+
|
|
|
|
|
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
|
|
|
|
+ response.setCharacterEncoding(CharsetUtil.UTF_8.name());
|
|
|
|
|
+ response.getWriter().write(objectMapper.writeValueAsString(R.failed(ex)));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|