浏览代码

feat:全模块请求体加密

李亚非 2 月之前
父节点
当前提交
07f574beba

+ 2 - 2
alien-util/src/main/java/shop/alien/util/encryption/EncryptTypeHandler.java

@@ -28,7 +28,7 @@ public class EncryptTypeHandler extends BaseTypeHandler<String> {
     @Override
     public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
         EncryptProperties properties = SpringContextUtil.getBean(EncryptProperties.class);
-        if (properties != null && properties.isEnabled() && parameter != null) {
+        if (properties != null && properties.isDbEnabled() && parameter != null) {
             String encrypted = StandardAesUtil.encrypt(parameter, properties.getKey(), properties.getIv());
             ps.setString(i, encrypted);
         } else {
@@ -56,7 +56,7 @@ public class EncryptTypeHandler extends BaseTypeHandler<String> {
             return value;
         }
         EncryptProperties properties = SpringContextUtil.getBean(EncryptProperties.class);
-        if (properties != null && properties.isEnabled()) {
+        if (properties != null && properties.isDbEnabled()) {
             try {
                 String decrypted = StandardAesUtil.decrypt(value, properties.getKey(), properties.getIv());
                 return decrypted != null ? decrypted : value;

+ 7 - 1
alien-util/src/main/java/shop/alien/util/encryption/StandardAesUtil.java

@@ -36,6 +36,11 @@ public class StandardAesUtil {
 
     public static String decrypt(String base64Data, String key, String iv) {
         try {
+            if (base64Data == null || base64Data.isEmpty()) {
+                return null;
+            }
+            log.debug("AES解密尝试: data_len={}, key_len={}, iv_len={}", base64Data.length(), key == null ? 0 : key.length(), iv == null ? 0 : iv.length());
+            
             Cipher cipher = Cipher.getInstance(ALGORITHM);
             SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
             IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
@@ -45,7 +50,8 @@ public class StandardAesUtil {
             byte[] original = cipher.doFinal(decoded);
             return new String(original, StandardCharsets.UTF_8);
         } catch (Exception e) {
-            log.error("AES解密失败: {}", e.getMessage());
+            log.error("AES解密失败: data_preview={}, error={}", 
+                      base64Data.substring(0, Math.min(base64Data.length(), 20)), e.getMessage());
             return null;
         }
     }

+ 43 - 5
alien-util/src/main/java/shop/alien/util/encryption/advice/DecryptRequestBodyAdvice.java

@@ -5,18 +5,20 @@ import org.springframework.core.MethodParameter;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpInputMessage;
 import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.util.AntPathMatcher;
 import org.springframework.util.StreamUtils;
 import org.springframework.web.bind.annotation.ControllerAdvice;
 import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
-import shop.alien.util.encryption.Decrypt;
 import shop.alien.util.encryption.StandardAesUtil;
 import shop.alien.util.encryption.properties.EncryptProperties;
 
+import javax.servlet.http.HttpServletRequest;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Type;
 import java.nio.charset.StandardCharsets;
+import java.util.List;
 
 /**
  * 请求体解密 Advice
@@ -28,12 +30,31 @@ public class DecryptRequestBodyAdvice extends RequestBodyAdviceAdapter {
     @Autowired
     private EncryptProperties encryptProperties;
 
+    @Autowired
+    private HttpServletRequest request;
+
+    private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
+
     @Override
     public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
-        // 只有开启了配置,且方法或类上有 @Decrypt 注解时才拦截
-        return encryptProperties.isEnabled() && 
-               (methodParameter.hasMethodAnnotation(Decrypt.class) || 
-                methodParameter.getContainingClass().isAnnotationPresent(Decrypt.class));
+        // 1. 如果接口加密关闭,则不解密
+        if (!encryptProperties.isApiEnabled()) {
+            return false;
+        }
+
+        // 2. 校验路径排除
+        String uri = request.getRequestURI();
+        List<String> excludePaths = encryptProperties.getExcludePaths();
+        if (excludePaths != null && !excludePaths.isEmpty()) {
+            for (String pattern : excludePaths) {
+                if (PATH_MATCHER.match(pattern, uri)) {
+                    return false;
+                }
+            }
+        }
+
+        // 3. 默认全开启
+        return true;
     }
 
     @Override
@@ -41,6 +62,23 @@ public class DecryptRequestBodyAdvice extends RequestBodyAdviceAdapter {
         // 读取加密的请求体
         byte[] bodyBytes = StreamUtils.copyToByteArray(inputMessage.getBody());
         String encryptedData = new String(bodyBytes, StandardCharsets.UTF_8);
+
+        // 核心优化:判断是否为明文 JSON。如果是明文 JSON,则直接跳过解密
+        String trimmedData = encryptedData.trim();
+        if ((trimmedData.startsWith("{") && (trimmedData.contains("\":") || trimmedData.endsWith("}"))) ||
+            (trimmedData.startsWith("[") && (trimmedData.contains("\":") || trimmedData.endsWith("]")))) {
+            return new HttpInputMessage() {
+                @Override
+                public InputStream getBody() throws IOException {
+                    return new ByteArrayInputStream(bodyBytes);
+                }
+
+                @Override
+                public HttpHeaders getHeaders() {
+                    return inputMessage.getHeaders();
+                }
+            };
+        }
         
         // 如果是 JSON 字符串格式(带有双引号),先去掉前后的双引号
         if (encryptedData.startsWith("\"") && encryptedData.endsWith("\"")) {

+ 26 - 5
alien-util/src/main/java/shop/alien/util/encryption/advice/EncryptResponseBodyAdvice.java

@@ -7,12 +7,15 @@ import org.springframework.http.MediaType;
 import org.springframework.http.converter.HttpMessageConverter;
 import org.springframework.http.server.ServerHttpRequest;
 import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.util.AntPathMatcher;
 import org.springframework.web.bind.annotation.ControllerAdvice;
 import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
-import shop.alien.util.encryption.Encrypt;
 import shop.alien.util.encryption.StandardAesUtil;
 import shop.alien.util.encryption.properties.EncryptProperties;
 
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+
 /**
  * 响应体加密 Advice
  * 拦截带有 @Encrypt 注解的控制器方法,将返回结果 AES 加密后返回
@@ -23,12 +26,30 @@ public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
     @Autowired
     private EncryptProperties encryptProperties;
 
+    @Autowired
+    private HttpServletRequest request;
+
+    private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
+
     @Override
     public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
-        // 只有开启了配置,且方法或类上有 @Encrypt 注解时才拦截
-        return encryptProperties.isEnabled() && 
-               (returnType.hasMethodAnnotation(Encrypt.class) || 
-                returnType.getContainingClass().isAnnotationPresent(Encrypt.class));
+        // 1. 如果接口加密关闭,不加密
+        if (!encryptProperties.isApiEnabled()) {
+            return false;
+        }
+
+        // 2. 路径排除校验
+        String uri = request.getRequestURI();
+        List<String> excludePaths = encryptProperties.getExcludePaths();
+        if (excludePaths != null && !excludePaths.isEmpty()) {
+            for (String pattern : excludePaths) {
+                if (PATH_MATCHER.match(pattern, uri)) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
     }
 
     @Override

+ 29 - 1
alien-util/src/main/java/shop/alien/util/encryption/properties/EncryptProperties.java

@@ -14,11 +14,35 @@ import org.springframework.stereotype.Component;
 public class EncryptProperties {
 
     /**
-     * 是否开启加解密
+     * 是否开启全局加解密(总开关)
      */
     private boolean enabled = true;
 
     /**
+     * 是否开启 API 接口加解密(请求体、响应体、参数)
+     */
+    private boolean apiEnabled = true;
+
+    /**
+     * 是否开启数据库字段加解密
+     */
+    private boolean dbEnabled = true;
+
+    /**
+     * 判断 API 加密是否生效
+     */
+    public boolean isApiEnabled() {
+        return enabled && apiEnabled;
+    }
+
+    /**
+     * 判断数据库加密是否生效
+     */
+    public boolean isDbEnabled() {
+        return enabled && dbEnabled;
+    }
+
+    /**
      * AES 密钥 (16位)
      */
     private String key;
@@ -27,4 +51,8 @@ public class EncryptProperties {
      * AES IV (16位)
      */
     private String iv;
+    /**
+     * 排除解密的路径集合(支持 AntPath 格式,如 /api/v1/user/**)
+     */
+    private java.util.List<String> excludePaths = new java.util.ArrayList<>();
 }