Sfoglia il codice sorgente

数据加解密逻辑优化

woody 11 mesi fa
parent
commit
7a573c8039
36 ha cambiato i file con 658 aggiunte e 430 eliminazioni
  1. 43 0
      framework-base/src/main/java/com/chelvc/framework/base/config/ApplicationConfigurer.java
  2. 0 70
      framework-base/src/main/java/com/chelvc/framework/base/config/ContextConfigurer.java
  3. 0 44
      framework-base/src/main/java/com/chelvc/framework/base/interceptor/ResponseHandleInterceptor.java
  4. 0 32
      framework-base/src/main/java/com/chelvc/framework/base/interceptor/ResponseHandler.java
  5. 26 8
      framework-base/src/main/java/com/chelvc/framework/base/interceptor/ResponseWrapInterceptor.java
  6. 44 0
      framework-common/src/main/java/com/chelvc/framework/common/crypto/AESCipherFactory.java
  7. 44 0
      framework-common/src/main/java/com/chelvc/framework/common/crypto/CipherFactory.java
  8. 143 9
      framework-common/src/main/java/com/chelvc/framework/common/util/AESUtils.java
  9. 10 0
      framework-database/src/main/java/com/chelvc/framework/database/config/DatabaseConfigurer.java
  10. 18 35
      framework-database/src/main/java/com/chelvc/framework/database/context/DatabaseContextHolder.java
  11. 25 0
      framework-database/src/main/java/com/chelvc/framework/database/context/DatabaseCryptoContext.java
  12. 30 0
      framework-database/src/main/java/com/chelvc/framework/database/context/DefaultDatabaseCryptoContext.java
  13. 8 8
      framework-database/src/main/java/com/chelvc/framework/database/handler/JsonTypeHandler.java
  14. 4 4
      framework-database/src/main/java/com/chelvc/framework/database/handler/SensitiveStringArrayTypeHandler.java
  15. 4 4
      framework-database/src/main/java/com/chelvc/framework/database/handler/SensitiveStringSetTypeHandler.java
  16. 4 4
      framework-database/src/main/java/com/chelvc/framework/database/handler/SensitiveStringsTypeHandler.java
  17. 4 4
      framework-database/src/main/java/com/chelvc/framework/database/handler/SensitiveTypeHandler.java
  18. 9 5
      framework-database/src/main/java/com/chelvc/framework/database/interceptor/DynamicInvokeInterceptor.java
  19. 1 1
      framework-database/src/main/java/com/chelvc/framework/database/sql/CallableStringDecrypter.java
  20. 1 1
      framework-database/src/main/java/com/chelvc/framework/database/sql/ResultStringDecrypter.java
  21. 1 1
      framework-database/src/main/java/com/chelvc/framework/database/sql/WriteStringEncryptor.java
  22. 0 1
      framework-export/src/main/java/com/chelvc/framework/export/support/DefaultExportHandler.java
  23. 21 0
      framework-oauth/src/main/java/com/chelvc/framework/oauth/config/OAuthProperties.java
  24. 10 2
      framework-oauth/src/main/java/com/chelvc/framework/oauth/token/RedisTokenValidator.java
  25. 16 0
      framework-security/src/main/java/com/chelvc/framework/security/config/SecurityConfigurer.java
  26. 5 20
      framework-security/src/main/java/com/chelvc/framework/security/config/SecurityProperties.java
  27. 17 0
      framework-security/src/main/java/com/chelvc/framework/security/context/DefaultSecurityCryptoContext.java
  28. 13 105
      framework-security/src/main/java/com/chelvc/framework/security/context/SecurityContextHolder.java
  29. 12 0
      framework-security/src/main/java/com/chelvc/framework/security/context/SecurityCryptoContext.java
  30. 70 31
      framework-security/src/main/java/com/chelvc/framework/security/interceptor/ControllerCryptoInterceptor.java
  31. 18 1
      framework-security/src/main/java/com/chelvc/framework/security/interceptor/SecurityValidateInterceptor.java
  32. 53 28
      framework-security/src/main/java/com/chelvc/framework/security/interceptor/SensitiveResponseInterceptor.java
  33. 1 3
      framework-security/src/main/java/com/chelvc/framework/security/serializer/SensitiveArraySerializer.java
  34. 1 3
      framework-security/src/main/java/com/chelvc/framework/security/serializer/SensitiveListSerializer.java
  35. 1 3
      framework-security/src/main/java/com/chelvc/framework/security/serializer/SensitiveSerializer.java
  36. 1 3
      framework-security/src/main/java/com/chelvc/framework/security/serializer/SensitiveSetSerializer.java

+ 43 - 0
framework-base/src/main/java/com/chelvc/framework/base/config/ApplicationConfigurer.java

@@ -0,0 +1,43 @@
+package com.chelvc.framework.base.config;
+
+import java.util.List;
+
+import com.chelvc.framework.base.context.DefaultSessionFactory;
+import com.chelvc.framework.base.context.SessionFactory;
+import com.chelvc.framework.base.interceptor.ResponseWrapInterceptor;
+import com.chelvc.framework.common.util.ObjectUtils;
+import com.google.common.collect.Lists;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
+import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
+
+/**
+ * 上下文配置
+ *
+ * @author Woody
+ * @date 2024/1/30
+ */
+@Configuration
+public class ApplicationConfigurer {
+    @Bean
+    @ConditionalOnMissingBean(SessionFactory.class)
+    public SessionFactory sessionFactory() {
+        return new DefaultSessionFactory();
+    }
+
+    @Bean
+    @ConditionalOnMissingBean(ResponseWrapInterceptor.class)
+    public ResponseWrapInterceptor responseWrapInterceptor(RequestMappingHandlerAdapter adapter) {
+        List<HandlerMethodReturnValueHandler> handlers =
+                ObjectUtils.ifNull(adapter.getReturnValueHandlers(), Lists::newArrayList, Lists::newArrayList);
+        HandlerMethodReturnValueHandler processor = handlers.stream()
+                .filter(handler -> handler instanceof RequestResponseBodyMethodProcessor).findAny().orElse(null);
+        ResponseWrapInterceptor interceptor = new ResponseWrapInterceptor(processor);
+        handlers.add(0, interceptor);
+        adapter.setReturnValueHandlers(handlers);
+        return interceptor;
+    }
+}

+ 0 - 70
framework-base/src/main/java/com/chelvc/framework/base/config/ContextConfigurer.java

@@ -1,70 +0,0 @@
-package com.chelvc.framework.base.config;
-
-import java.util.List;
-
-import com.chelvc.framework.base.context.ApplicationContextHolder;
-import com.chelvc.framework.base.context.DefaultSessionFactory;
-import com.chelvc.framework.base.context.SessionFactory;
-import com.chelvc.framework.base.interceptor.ResponseHandleInterceptor;
-import com.chelvc.framework.base.interceptor.ResponseHandler;
-import com.chelvc.framework.base.interceptor.ResponseWrapHandler;
-import com.chelvc.framework.common.util.ObjectUtils;
-import com.google.common.collect.Lists;
-import org.springframework.beans.BeansException;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.util.CollectionUtils;
-import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
-import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
-import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
-
-/**
- * 上下文配置
- *
- * @author Woody
- * @date 2024/1/30
- */
-@Configuration
-public class ContextConfigurer implements ApplicationContextAware {
-    /**
-     * 初始化响应结果处理拦截器
-     *
-     * @param applicationContext 应用上下文
-     */
-    private void initializeResponseHandleInterceptor(ApplicationContext applicationContext) {
-        RequestMappingHandlerAdapter adapter = applicationContext.getBean(RequestMappingHandlerAdapter.class);
-        List<HandlerMethodReturnValueHandler> valueHandlers = adapter.getReturnValueHandlers();
-        RequestResponseBodyMethodProcessor processor = CollectionUtils.isEmpty(valueHandlers) ? null :
-                valueHandlers.stream().filter(handler -> handler instanceof RequestResponseBodyMethodProcessor)
-                        .map(handler -> (RequestResponseBodyMethodProcessor) handler).findFirst().orElse(null);
-        List<ResponseHandler> responseHandlers =
-                ApplicationContextHolder.getOrderBeans(applicationContext, ResponseHandler.class);
-        ResponseHandleInterceptor interceptor = new ResponseHandleInterceptor(processor, responseHandlers);
-        List<HandlerMethodReturnValueHandler> handlers = Lists.newLinkedList();
-        handlers.add(interceptor);
-        if (ObjectUtils.notEmpty(valueHandlers)) {
-            handlers.addAll(valueHandlers);
-        }
-        adapter.setReturnValueHandlers(handlers);
-    }
-
-    @Override
-    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
-        this.initializeResponseHandleInterceptor(applicationContext);
-    }
-
-    @Bean
-    @ConditionalOnMissingBean(SessionFactory.class)
-    public SessionFactory sessionFactory() {
-        return new DefaultSessionFactory();
-    }
-
-    @Bean
-    @ConditionalOnMissingBean(ResponseWrapHandler.class)
-    public ResponseWrapHandler responseWrapHandler() {
-        return new ResponseWrapHandler();
-    }
-}

+ 0 - 44
framework-base/src/main/java/com/chelvc/framework/base/interceptor/ResponseHandleInterceptor.java

@@ -1,44 +0,0 @@
-package com.chelvc.framework.base.interceptor;
-
-import java.util.List;
-
-import com.google.common.collect.Lists;
-import lombok.NonNull;
-import org.springframework.core.MethodParameter;
-import org.springframework.web.context.request.NativeWebRequest;
-import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
-import org.springframework.web.method.support.ModelAndViewContainer;
-import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
-
-/**
- * 响应结果处理拦截器
- *
- * @author Woody
- * @date 2024/1/30
- */
-public class ResponseHandleInterceptor implements HandlerMethodReturnValueHandler {
-    private final RequestResponseBodyMethodProcessor processor;
-    private final List<ResponseHandler> handlers;
-
-    public ResponseHandleInterceptor(@NonNull RequestResponseBodyMethodProcessor processor,
-                                     @NonNull List<ResponseHandler> handlers) {
-        this.processor = processor;
-        this.handlers = Lists.newArrayList(handlers);
-    }
-
-    @Override
-    public boolean supportsReturnType(MethodParameter method) {
-        return true;
-    }
-
-    @Override
-    public void handleReturnValue(Object value, MethodParameter method, ModelAndViewContainer container,
-                                  NativeWebRequest request) throws Exception {
-        for (ResponseHandler handler : this.handlers) {
-            if (handler.supports(method)) {
-                value = handler.handle(request, method, value);
-            }
-        }
-        this.processor.handleReturnValue(value, method, container, request);
-    }
-}

+ 0 - 32
framework-base/src/main/java/com/chelvc/framework/base/interceptor/ResponseHandler.java

@@ -1,32 +0,0 @@
-package com.chelvc.framework.base.interceptor;
-
-import org.springframework.core.MethodParameter;
-import org.springframework.web.context.request.NativeWebRequest;
-
-/**
- * 响应结果处理器接口
- *
- * @author Woody
- * @date 2024/1/30
- */
-public interface ResponseHandler {
-    /**
-     * 判断当前处理器是否支持指定方法
-     *
-     * @param method 请求方法
-     * @return true/false
-     */
-    default boolean supports(MethodParameter method) {
-        return true;
-    }
-
-    /**
-     * 响应结果处理
-     *
-     * @param request 请求对象
-     * @param method  请求方法
-     * @param value   请求结果
-     * @return 处理后结果
-     */
-    Object handle(NativeWebRequest request, MethodParameter method, Object value);
-}

+ 26 - 8
framework-base/src/main/java/com/chelvc/framework/base/interceptor/ResponseWrapHandler.java → framework-base/src/main/java/com/chelvc/framework/base/interceptor/ResponseWrapInterceptor.java

@@ -1,13 +1,18 @@
 package com.chelvc.framework.base.interceptor;
 
+import java.util.Objects;
+import javax.servlet.http.HttpServletResponse;
+
 import com.chelvc.framework.base.annotation.ResponseWrapping;
 import com.chelvc.framework.base.context.ApplicationContextHolder;
 import com.chelvc.framework.base.context.Result;
+import com.chelvc.framework.base.context.SessionContextHolder;
 import org.springframework.core.MethodParameter;
-import org.springframework.core.annotation.Order;
 import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
+import org.springframework.web.method.support.ModelAndViewContainer;
 
 /**
  * 响应结果包装处理器实现
@@ -15,8 +20,13 @@ import org.springframework.web.context.request.NativeWebRequest;
  * @author Woody
  * @date 2024/1/30
  */
-@Order
-public class ResponseWrapHandler implements ResponseHandler {
+public class ResponseWrapInterceptor implements HandlerMethodReturnValueHandler {
+    private final HandlerMethodReturnValueHandler handler;
+
+    public ResponseWrapInterceptor(HandlerMethodReturnValueHandler handler) {
+        this.handler = handler;
+    }
+
     /**
      * 判断目标方法是否使用@ResponseBody注解
      *
@@ -48,8 +58,8 @@ public class ResponseWrapHandler implements ResponseHandler {
      *
      * @param request 请求对象
      * @param method  请求方法
-     * @param value   请求结果
-     * @return 请求结果对象实例
+     * @param value   结果
+     * @return 结果包装对象实例
      */
     protected Result<?> wrap(NativeWebRequest request, MethodParameter method, Object value) {
         if (value instanceof Result) {
@@ -59,12 +69,20 @@ public class ResponseWrapHandler implements ResponseHandler {
     }
 
     @Override
-    public boolean supports(MethodParameter method) {
+    public boolean supportsReturnType(MethodParameter method) {
         return this.isResponseBodyMethod(method) && this.isResponseWrappingMethod(method);
     }
 
     @Override
-    public Object handle(NativeWebRequest request, MethodParameter method, Object value) {
-        return this.wrap(request, method, value);
+    public void handleReturnValue(Object value, MethodParameter method, ModelAndViewContainer container,
+                                  NativeWebRequest request) throws Exception {
+        Result<?> result = this.wrap(request, method, value);
+        if (this.handler == null) {
+            container.setRequestHandled(true);
+            HttpServletResponse response = (HttpServletResponse) request.getNativeResponse();
+            SessionContextHolder.response(Objects.requireNonNull(response), result);
+        } else {
+            this.handler.handleReturnValue(result, method, container, request);
+        }
     }
 }

+ 44 - 0
framework-common/src/main/java/com/chelvc/framework/common/crypto/AESCipherFactory.java

@@ -0,0 +1,44 @@
+package com.chelvc.framework.common.crypto;
+
+import javax.crypto.Cipher;
+
+import com.chelvc.framework.common.util.AESUtils;
+import lombok.NonNull;
+
+/**
+ * AES加解密处理器工厂实现
+ *
+ * @author Woody
+ * @date 2024/6/25
+ */
+public class AESCipherFactory implements CipherFactory {
+    private final String key;
+    private final String name;
+    private final String secret;
+    private final String iv;
+
+    public AESCipherFactory(@NonNull String secret) {
+        this(secret, AESUtils.secret2iv(secret));
+    }
+
+    public AESCipherFactory(@NonNull String secret, @NonNull String iv) {
+        this(AESUtils.CBC_PKCS5PADDING, secret, iv);
+    }
+
+    public AESCipherFactory(@NonNull String name, @NonNull String secret, @NonNull String iv) {
+        this.key = name + secret;
+        this.name = name;
+        this.secret = secret;
+        this.iv = iv;
+    }
+
+    @Override
+    public String getSecret() {
+        return this.secret;
+    }
+
+    @Override
+    public Cipher getCipher(int mode) {
+        return AESUtils.lookupCipher(this.key, mode, () -> AESUtils.getCipher(this.name, mode, this.secret, this.iv));
+    }
+}

+ 44 - 0
framework-common/src/main/java/com/chelvc/framework/common/crypto/CipherFactory.java

@@ -0,0 +1,44 @@
+package com.chelvc.framework.common.crypto;
+
+import javax.crypto.Cipher;
+
+/**
+ * 密码处理器工厂接口
+ *
+ * @author Woody
+ * @date 2024/6/25
+ */
+public interface CipherFactory {
+    /**
+     * 获取密钥
+     *
+     * @return 密钥
+     */
+    String getSecret();
+
+    /**
+     * 获取密码处理器
+     *
+     * @param mode 加解密模式
+     * @return 密码处理器实例
+     */
+    Cipher getCipher(int mode);
+
+    /**
+     * 获取加密处理器
+     *
+     * @return 加密处理器
+     */
+    default Cipher getEncryptor() {
+        return this.getCipher(Cipher.ENCRYPT_MODE);
+    }
+
+    /**
+     * 获取解密处理器
+     *
+     * @return 解密处理器
+     */
+    default Cipher getDecrypter() {
+        return this.getCipher(Cipher.DECRYPT_MODE);
+    }
+}

+ 143 - 9
framework-common/src/main/java/com/chelvc/framework/common/util/AESUtils.java

@@ -18,6 +18,7 @@ import javax.crypto.spec.SecretKeySpec;
 import com.chelvc.framework.common.model.Pool;
 import com.google.common.collect.Maps;
 import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.codec.binary.Base64;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
@@ -27,6 +28,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
  * @author woody
  * @date 2024/1/30
  */
+@Slf4j
 public final class AESUtils {
     /**
      * 密钥长度
@@ -75,6 +77,21 @@ public final class AESUtils {
     private AESUtils() {
     }
 
+    /**
+     * 将密钥转换成向量
+     *
+     * @param secret 密钥
+     * @return 向量
+     */
+    public static String secret2iv(@NonNull String secret) {
+        int size = Math.min(secret.length(), 16);
+        StringBuilder iv = new StringBuilder(size);
+        for (int i = secret.length() - 1, max = 0; i >= 0 && max < size; i--, max++) {
+            iv.append(secret.charAt(i));
+        }
+        return iv.toString();
+    }
+
     /**
      * 获取密码处理器
      *
@@ -376,7 +393,30 @@ public final class AESUtils {
      * @return 密文(base64)
      */
     public static String encrypt(@NonNull Cipher cipher, String plaintext) {
-        return plaintext == null ? null : encrypt(cipher, plaintext.getBytes(StandardCharsets.UTF_8));
+        return encrypt(cipher, plaintext, false);
+    }
+
+    /**
+     * AES加密
+     *
+     * @param cipher    密码处理器
+     * @param plaintext 明文
+     * @param force     是否忽略异常
+     * @return 密文(base64)
+     */
+    public static String encrypt(@NonNull Cipher cipher, String plaintext, boolean force) {
+        if (plaintext == null) {
+            return null;
+        }
+        try {
+            return encrypt(cipher, plaintext.getBytes(StandardCharsets.UTF_8));
+        } catch (Exception e) {
+            if (!force) {
+                throw e;
+            }
+            log.warn("Plaintext encrypt failed: {}, {}", plaintext, e.getMessage());
+        }
+        return plaintext;
     }
 
     /**
@@ -398,8 +438,30 @@ public final class AESUtils {
      * @return 明文
      */
     public static String decrypt(@NonNull Cipher cipher, String ciphertext) {
-        byte[] bytes = ObjectUtils.ifNull(ciphertext, Base64::decodeBase64);
-        return bytes == null || bytes.length == 0 ? ciphertext : decrypt(cipher, bytes);
+        return decrypt(cipher, ciphertext, false);
+    }
+
+    /**
+     * AES解密
+     *
+     * @param cipher     密码处理器
+     * @param ciphertext 密文(base64)
+     * @param force      是否忽略异常
+     * @return 明文
+     */
+    public static String decrypt(@NonNull Cipher cipher, String ciphertext, boolean force) {
+        if (ciphertext == null) {
+            return null;
+        }
+        try {
+            return decrypt(cipher, Base64.decodeBase64(ciphertext));
+        } catch (Exception e) {
+            if (!force) {
+                throw e;
+            }
+            log.warn("Ciphertext decrypt failed: {}, {}", ciphertext, e.getMessage());
+        }
+        return ciphertext;
     }
 
     /**
@@ -421,12 +483,24 @@ public final class AESUtils {
      * @return 密文数组
      */
     public static String[] encrypt(@NonNull Cipher cipher, String... plaintexts) {
+        return encrypt(cipher, plaintexts, false);
+    }
+
+    /**
+     * 数据加密
+     *
+     * @param cipher     密码处理器
+     * @param plaintexts 明文数组
+     * @param force      是否忽略异常
+     * @return 密文数组
+     */
+    public static String[] encrypt(@NonNull Cipher cipher, String[] plaintexts, boolean force) {
         if (ObjectUtils.isEmpty(plaintexts)) {
             return plaintexts;
         }
         String[] ciphertexts = new String[plaintexts.length];
         for (int i = 0; i < plaintexts.length; i++) {
-            ciphertexts[i] = encrypt(cipher, plaintexts[i]);
+            ciphertexts[i] = encrypt(cipher, plaintexts[i], force);
         }
         return ciphertexts;
     }
@@ -439,10 +513,22 @@ public final class AESUtils {
      * @return 密文集合
      */
     public static Set<String> encrypt(@NonNull Cipher cipher, Set<String> plaintexts) {
+        return encrypt(cipher, plaintexts, false);
+    }
+
+    /**
+     * 数据加密
+     *
+     * @param cipher     密码处理器
+     * @param plaintexts 明文集合
+     * @param force      是否忽略异常
+     * @return 密文集合
+     */
+    public static Set<String> encrypt(@NonNull Cipher cipher, Set<String> plaintexts, boolean force) {
         if (ObjectUtils.isEmpty(plaintexts)) {
             return plaintexts;
         }
-        return plaintexts.stream().map(plaintext -> encrypt(cipher, plaintext)).collect(Collectors.toSet());
+        return plaintexts.stream().map(plaintext -> encrypt(cipher, plaintext, force)).collect(Collectors.toSet());
     }
 
     /**
@@ -453,10 +539,22 @@ public final class AESUtils {
      * @return 密文列表
      */
     public static List<String> encrypt(@NonNull Cipher cipher, List<String> plaintexts) {
+        return encrypt(cipher, plaintexts, false);
+    }
+
+    /**
+     * 数据加密
+     *
+     * @param cipher     密码处理器
+     * @param plaintexts 明文列表
+     * @param force      是否忽略异常
+     * @return 密文列表
+     */
+    public static List<String> encrypt(@NonNull Cipher cipher, List<String> plaintexts, boolean force) {
         if (ObjectUtils.isEmpty(plaintexts)) {
             return plaintexts;
         }
-        return plaintexts.stream().map(plaintext -> encrypt(cipher, plaintext)).collect(Collectors.toList());
+        return plaintexts.stream().map(plaintext -> encrypt(cipher, plaintext, force)).collect(Collectors.toList());
     }
 
     /**
@@ -467,12 +565,24 @@ public final class AESUtils {
      * @return 明文数组
      */
     public static String[] decrypt(@NonNull Cipher cipher, String... ciphertexts) {
+        return decrypt(cipher, ciphertexts, false);
+    }
+
+    /**
+     * 数据解密
+     *
+     * @param cipher      密码处理器
+     * @param ciphertexts 密文数组
+     * @param force       是否忽略异常
+     * @return 明文数组
+     */
+    public static String[] decrypt(@NonNull Cipher cipher, String[] ciphertexts, boolean force) {
         if (ObjectUtils.isEmpty(ciphertexts)) {
             return ciphertexts;
         }
         String[] plaintexts = new String[ciphertexts.length];
         for (int i = 0; i < ciphertexts.length; i++) {
-            plaintexts[i] = decrypt(cipher, ciphertexts[i]);
+            plaintexts[i] = decrypt(cipher, ciphertexts[i], force);
         }
         return plaintexts;
     }
@@ -485,10 +595,22 @@ public final class AESUtils {
      * @return 明文集合
      */
     public static Set<String> decrypt(@NonNull Cipher cipher, Set<String> ciphertexts) {
+        return decrypt(cipher, ciphertexts, false);
+    }
+
+    /**
+     * 数据解密
+     *
+     * @param cipher      密码处理器
+     * @param ciphertexts 密文集合
+     * @param force       是否忽略异常
+     * @return 明文集合
+     */
+    public static Set<String> decrypt(@NonNull Cipher cipher, Set<String> ciphertexts, boolean force) {
         if (ObjectUtils.isEmpty(ciphertexts)) {
             return ciphertexts;
         }
-        return ciphertexts.stream().map(ciphertext -> decrypt(cipher, ciphertext)).collect(Collectors.toSet());
+        return ciphertexts.stream().map(ciphertext -> decrypt(cipher, ciphertext, force)).collect(Collectors.toSet());
     }
 
     /**
@@ -499,9 +621,21 @@ public final class AESUtils {
      * @return 明文列表
      */
     public static List<String> decrypt(@NonNull Cipher cipher, List<String> ciphertexts) {
+        return decrypt(cipher, ciphertexts, false);
+    }
+
+    /**
+     * 数据解密
+     *
+     * @param cipher      密码处理器
+     * @param ciphertexts 密文列表
+     * @param force       是否忽略异常
+     * @return 明文列表
+     */
+    public static List<String> decrypt(@NonNull Cipher cipher, List<String> ciphertexts, boolean force) {
         if (ObjectUtils.isEmpty(ciphertexts)) {
             return ciphertexts;
         }
-        return ciphertexts.stream().map(ciphertext -> decrypt(cipher, ciphertext)).collect(Collectors.toList());
+        return ciphertexts.stream().map(ciphertext -> decrypt(cipher, ciphertext, force)).collect(Collectors.toList());
     }
 }

+ 10 - 0
framework-database/src/main/java/com/chelvc/framework/database/config/DatabaseConfigurer.java

@@ -16,6 +16,8 @@ import com.chelvc.framework.common.model.File;
 import com.chelvc.framework.common.model.Modification;
 import com.chelvc.framework.common.model.Period;
 import com.chelvc.framework.common.model.Region;
+import com.chelvc.framework.database.context.DatabaseCryptoContext;
+import com.chelvc.framework.database.context.DefaultDatabaseCryptoContext;
 import com.chelvc.framework.database.context.Transactor;
 import com.chelvc.framework.database.handler.FileTypeHandler;
 import com.chelvc.framework.database.handler.ModificationTypeHandler;
@@ -29,6 +31,7 @@ import lombok.RequiredArgsConstructor;
 import org.apache.ibatis.type.JdbcType;
 import org.apache.ibatis.type.TypeHandlerRegistry;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -48,6 +51,7 @@ public class DatabaseConfigurer {
         MybatisConfigurer.initialize();
     }
 
+    private final DatabaseProperties properties;
     private final ApplicationContext applicationContext;
 
     @Bean
@@ -111,6 +115,12 @@ public class DatabaseConfigurer {
         return interceptor;
     }
 
+    @Bean
+    @ConditionalOnMissingBean(DatabaseCryptoContext.class)
+    public DatabaseCryptoContext databaseCryptoContext() {
+        return new DefaultDatabaseCryptoContext(this.properties);
+    }
+
     @Bean
     public ConfigurationCustomizer configurationCustomizer() {
         return configuration -> {

+ 18 - 35
framework-database/src/main/java/com/chelvc/framework/database/context/DatabaseContextHolder.java

@@ -34,10 +34,8 @@ import com.chelvc.framework.common.function.Handler;
 import com.chelvc.framework.common.function.Provider;
 import com.chelvc.framework.common.model.Pagination;
 import com.chelvc.framework.common.model.Paging;
-import com.chelvc.framework.common.util.AESUtils;
 import com.chelvc.framework.common.util.AssertUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
-import com.chelvc.framework.database.config.DatabaseProperties;
 import com.chelvc.framework.database.entity.Entity;
 import com.chelvc.framework.database.support.EventService;
 import com.google.common.collect.Maps;
@@ -76,11 +74,6 @@ public final class DatabaseContextHolder {
     private static final Map<String, Map<String, TableFieldContext>> TABLE_FIELD_CONTEXT_MAPPING =
             Maps.newConcurrentMap();
 
-    /**
-     * 配置属性
-     */
-    private static DatabaseProperties PROPERTIES;
-
     /**
      * 事务处理器
      */
@@ -91,39 +84,29 @@ public final class DatabaseContextHolder {
      */
     private static SqlSessionTemplate SESSION_TEMPLATE;
 
+    /**
+     * 数据加解密上下文
+     */
+    private static DatabaseCryptoContext CRYPTO_CONTEXT;
+
+
     private DatabaseContextHolder() {
     }
 
     /**
-     * 获取配置属性
+     * 获取数据加解密上下文
      *
-     * @return 配置属性
+     * @return 数据加解密上下文实例
      */
-    private static DatabaseProperties getProperties() {
-        if (PROPERTIES == null) {
-            synchronized (DatabaseProperties.class) {
-                if (PROPERTIES == null) {
-                    PROPERTIES = ApplicationContextHolder.getBean(DatabaseProperties.class);
+    public static DatabaseCryptoContext getCryptoContext() {
+        if (CRYPTO_CONTEXT == null) {
+            synchronized (DatabaseCryptoContext.class) {
+                if (CRYPTO_CONTEXT == null) {
+                    CRYPTO_CONTEXT = ApplicationContextHolder.getBean(DatabaseCryptoContext.class);
                 }
             }
         }
-        return PROPERTIES;
-    }
-
-    /**
-     * 获取密码处理器
-     *
-     * @param mode 加解密模式
-     * @return 密码处理器实例
-     */
-    private static Cipher getCipher(int mode) {
-        DatabaseProperties.Sensitive sensitive = getProperties().getSensitive();
-        String secret = AssertUtils.nonempty(sensitive.getSecret(), () -> "Cipher secret is missing");
-        String iv = AssertUtils.nonempty(sensitive.getIv(), () -> "Cipher iv is missing");
-        return AESUtils.lookupCipher(
-                AESUtils.CBC_PKCS5PADDING + secret, mode,
-                () -> AESUtils.getCipher(AESUtils.CBC_PKCS5PADDING, mode, secret, iv)
-        );
+        return CRYPTO_CONTEXT;
     }
 
     /**
@@ -132,7 +115,7 @@ public final class DatabaseContextHolder {
      * @return 加密处理器
      */
     public static Cipher getEncryptor() {
-        return getCipher(Cipher.ENCRYPT_MODE);
+        return getCryptoContext().getEncryptor();
     }
 
     /**
@@ -141,7 +124,7 @@ public final class DatabaseContextHolder {
      * @return 解密处理器
      */
     public static Cipher getDecrypter() {
-        return getCipher(Cipher.DECRYPT_MODE);
+        return getCryptoContext().getDecrypter();
     }
 
     /**
@@ -150,7 +133,7 @@ public final class DatabaseContextHolder {
      * @return true/false
      */
     public static boolean isSensitiveMixed() {
-        return getProperties().getSensitive().isMixed();
+        return getCryptoContext().isSensitiveMixed();
     }
 
     /**
@@ -159,7 +142,7 @@ public final class DatabaseContextHolder {
      * @return true/false
      */
     public static boolean isSensitiveWritable() {
-        return getProperties().getSensitive().isWritable();
+        return getCryptoContext().isSensitiveWritable();
     }
 
     /**

+ 25 - 0
framework-database/src/main/java/com/chelvc/framework/database/context/DatabaseCryptoContext.java

@@ -0,0 +1,25 @@
+package com.chelvc.framework.database.context;
+
+import com.chelvc.framework.common.crypto.CipherFactory;
+
+/**
+ * 数据库数据加解密上下文接口
+ *
+ * @author Woody
+ * @date 2024/6/25
+ */
+public interface DatabaseCryptoContext extends CipherFactory {
+    /**
+     * 判断是否是敏感字段数据混合模式(同时包含明文、密文)
+     *
+     * @return true/false
+     */
+    boolean isSensitiveMixed();
+
+    /**
+     * 判断是否可写入密文
+     *
+     * @return true/false
+     */
+    boolean isSensitiveWritable();
+}

+ 30 - 0
framework-database/src/main/java/com/chelvc/framework/database/context/DefaultDatabaseCryptoContext.java

@@ -0,0 +1,30 @@
+package com.chelvc.framework.database.context;
+
+import com.chelvc.framework.common.crypto.AESCipherFactory;
+import com.chelvc.framework.database.config.DatabaseProperties;
+import lombok.NonNull;
+
+/**
+ * 数据库数据加解密上下文默认实现
+ *
+ * @author Woody
+ * @date 2024/6/25
+ */
+public class DefaultDatabaseCryptoContext extends AESCipherFactory implements DatabaseCryptoContext {
+    private final DatabaseProperties properties;
+
+    public DefaultDatabaseCryptoContext(@NonNull DatabaseProperties properties) {
+        super(properties.getSensitive().getSecret());
+        this.properties = properties;
+    }
+
+    @Override
+    public boolean isSensitiveMixed() {
+        return this.properties.getSensitive().isMixed();
+    }
+
+    @Override
+    public boolean isSensitiveWritable() {
+        return this.properties.getSensitive().isWritable();
+    }
+}

+ 8 - 8
framework-database/src/main/java/com/chelvc/framework/database/handler/JsonTypeHandler.java

@@ -53,7 +53,7 @@ public interface JsonTypeHandler<T> extends TypeHandler<T> {
         @Override
         public void serialize(String value, JsonGenerator generator, SerializerProvider provider) throws IOException {
             if (DatabaseContextHolder.isSensitiveWritable() && value != null) {
-                value = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), value);
+                value = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), value, true);
             }
             generator.writeString(value);
         }
@@ -67,7 +67,7 @@ public interface JsonTypeHandler<T> extends TypeHandler<T> {
         public void serialize(String[] value, JsonGenerator generator, SerializerProvider provider)
                 throws IOException {
             if (DatabaseContextHolder.isSensitiveWritable() && ObjectUtils.notEmpty(value)) {
-                value = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), value);
+                value = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), value, true);
             }
             generator.writeObject(value);
         }
@@ -81,7 +81,7 @@ public interface JsonTypeHandler<T> extends TypeHandler<T> {
         public void serialize(Set<String> value, JsonGenerator generator, SerializerProvider provider)
                 throws IOException {
             if (DatabaseContextHolder.isSensitiveWritable() && ObjectUtils.notEmpty(value)) {
-                value = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), value);
+                value = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), value, true);
             }
             generator.writeObject(value);
         }
@@ -95,7 +95,7 @@ public interface JsonTypeHandler<T> extends TypeHandler<T> {
         public void serialize(List<String> value, JsonGenerator generator, SerializerProvider provider)
                 throws IOException {
             if (DatabaseContextHolder.isSensitiveWritable() && ObjectUtils.notEmpty(value)) {
-                value = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), value);
+                value = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), value, true);
             }
             generator.writeObject(value);
         }
@@ -109,7 +109,7 @@ public interface JsonTypeHandler<T> extends TypeHandler<T> {
         public String deserialize(JsonParser parser, DeserializationContext context)
                 throws IOException, JsonProcessingException {
             String value = parser.getValueAsString();
-            return value == null ? null : AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), value);
+            return value == null ? null : AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), value, true);
         }
     }
 
@@ -128,7 +128,7 @@ public interface JsonTypeHandler<T> extends TypeHandler<T> {
             if (ObjectUtils.isEmpty(values)) {
                 return values;
             }
-            return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values);
+            return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values, true);
         }
     }
 
@@ -147,7 +147,7 @@ public interface JsonTypeHandler<T> extends TypeHandler<T> {
             if (ObjectUtils.isEmpty(values)) {
                 return values;
             }
-            return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values);
+            return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values, true);
         }
     }
 
@@ -166,7 +166,7 @@ public interface JsonTypeHandler<T> extends TypeHandler<T> {
             if (ObjectUtils.isEmpty(values)) {
                 return values;
             }
-            return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values);
+            return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values, true);
         }
     }
 

+ 4 - 4
framework-database/src/main/java/com/chelvc/framework/database/handler/SensitiveStringArrayTypeHandler.java

@@ -21,7 +21,7 @@ public class SensitiveStringArrayTypeHandler extends StringArrayTypeHandler {
     public void setNonNullParameter(PreparedStatement ps, int i, String[] parameter, JdbcType jdbcType)
             throws SQLException {
         if (DatabaseContextHolder.isSensitiveWritable() && ObjectUtils.notEmpty(parameter)) {
-            parameter = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), parameter);
+            parameter = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), parameter, true);
         }
         super.setNonNullParameter(ps, i, parameter, jdbcType);
     }
@@ -32,7 +32,7 @@ public class SensitiveStringArrayTypeHandler extends StringArrayTypeHandler {
         if (ObjectUtils.isEmpty(values)) {
             return values;
         }
-        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values);
+        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values, true);
     }
 
     @Override
@@ -41,7 +41,7 @@ public class SensitiveStringArrayTypeHandler extends StringArrayTypeHandler {
         if (ObjectUtils.isEmpty(values)) {
             return values;
         }
-        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values);
+        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values, true);
     }
 
     @Override
@@ -50,6 +50,6 @@ public class SensitiveStringArrayTypeHandler extends StringArrayTypeHandler {
         if (ObjectUtils.isEmpty(values)) {
             return values;
         }
-        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values);
+        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values, true);
     }
 }

+ 4 - 4
framework-database/src/main/java/com/chelvc/framework/database/handler/SensitiveStringSetTypeHandler.java

@@ -22,7 +22,7 @@ public class SensitiveStringSetTypeHandler extends StringSetTypeHandler {
     public void setNonNullParameter(PreparedStatement ps, int i, Set<String> parameter, JdbcType jdbcType)
             throws SQLException {
         if (DatabaseContextHolder.isSensitiveWritable() && ObjectUtils.notEmpty(parameter)) {
-            parameter = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), parameter);
+            parameter = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), parameter, true);
         }
         super.setNonNullParameter(ps, i, parameter, jdbcType);
     }
@@ -33,7 +33,7 @@ public class SensitiveStringSetTypeHandler extends StringSetTypeHandler {
         if (ObjectUtils.isEmpty(values)) {
             return values;
         }
-        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values);
+        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values, true);
     }
 
     @Override
@@ -42,7 +42,7 @@ public class SensitiveStringSetTypeHandler extends StringSetTypeHandler {
         if (ObjectUtils.isEmpty(values)) {
             return values;
         }
-        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values);
+        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values, true);
     }
 
     @Override
@@ -51,6 +51,6 @@ public class SensitiveStringSetTypeHandler extends StringSetTypeHandler {
         if (ObjectUtils.isEmpty(values)) {
             return values;
         }
-        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values);
+        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values, true);
     }
 }

+ 4 - 4
framework-database/src/main/java/com/chelvc/framework/database/handler/SensitiveStringsTypeHandler.java

@@ -22,7 +22,7 @@ public class SensitiveStringsTypeHandler extends StringsTypeHandler {
     public void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType)
             throws SQLException {
         if (DatabaseContextHolder.isSensitiveWritable() && ObjectUtils.notEmpty(parameter)) {
-            parameter = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), parameter);
+            parameter = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), parameter, true);
         }
         super.setNonNullParameter(ps, i, parameter, jdbcType);
     }
@@ -33,7 +33,7 @@ public class SensitiveStringsTypeHandler extends StringsTypeHandler {
         if (ObjectUtils.isEmpty(values)) {
             return values;
         }
-        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values);
+        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values, true);
     }
 
     @Override
@@ -42,7 +42,7 @@ public class SensitiveStringsTypeHandler extends StringsTypeHandler {
         if (ObjectUtils.isEmpty(values)) {
             return values;
         }
-        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values);
+        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values, true);
     }
 
     @Override
@@ -51,6 +51,6 @@ public class SensitiveStringsTypeHandler extends StringsTypeHandler {
         if (ObjectUtils.isEmpty(values)) {
             return values;
         }
-        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values);
+        return AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), values, true);
     }
 }

+ 4 - 4
framework-database/src/main/java/com/chelvc/framework/database/handler/SensitiveTypeHandler.java

@@ -21,7 +21,7 @@ public class SensitiveTypeHandler extends BaseTypeHandler<String> {
     public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
             throws SQLException {
         if (DatabaseContextHolder.isSensitiveWritable() && parameter != null) {
-            parameter = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), parameter);
+            parameter = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), parameter, true);
         }
         ps.setString(i, parameter);
     }
@@ -29,18 +29,18 @@ public class SensitiveTypeHandler extends BaseTypeHandler<String> {
     @Override
     public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
         String value = rs.getString(columnName);
-        return value == null ? null : AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), value);
+        return value == null ? null : AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), value, true);
     }
 
     @Override
     public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
         String value = rs.getString(columnIndex);
-        return value == null ? null : AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), value);
+        return value == null ? null : AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), value, true);
     }
 
     @Override
     public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
         String value = cs.getString(columnIndex);
-        return value == null ? null : AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), value);
+        return value == null ? null : AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), value, true);
     }
 }

+ 9 - 5
framework-database/src/main/java/com/chelvc/framework/database/interceptor/DynamicInvokeInterceptor.java

@@ -7,6 +7,7 @@ import java.util.Objects;
 import java.util.function.Consumer;
 
 import com.baomidou.mybatisplus.core.toolkit.StringPool;
+import com.chelvc.framework.common.util.AESUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.database.context.DatabaseContextHolder;
 import com.chelvc.framework.database.context.TableFieldContext;
@@ -780,7 +781,7 @@ public class DynamicInvokeInterceptor implements Interceptor {
      */
     private boolean initializeSensitiveParameter(BoundSql bound, Table table, Column column, JdbcParameter parameter,
                                                  Expression condition, Consumer<Expression> changing) {
-        // 针对敏感字段数据混合模式下参数精确匹配场景,将自动添加明文查询条件
+        // 针对敏感字段数据混合模式下参数精确匹配场景,将自动添加混合查询条件
         if (!DatabaseContextHolder.isSensitiveMixed() || !(condition instanceof EqualsTo
                 || condition instanceof NotEqualsTo || condition instanceof InExpression)) {
             return false;
@@ -793,20 +794,23 @@ public class DynamicInvokeInterceptor implements Interceptor {
             return false;
         }
 
-        // 获取参数明文并添加查询条件
+        // 获取参数明文并添加明文或密文查询条件
         Object value = context.handleWriteValue(this.getParameterValue(bound, parameter));
         if (!(value instanceof String)) {
             return false;
+        } else if (!DatabaseContextHolder.isSensitiveWritable()) {
+            // 如果未开启加密写入功能,则添加密文查询条件
+            value = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), (String) value, true);
         }
-        StringValue plaintext = new StringValue(value.toString());
+        StringValue target = new StringValue((String) value);
         if (condition instanceof InExpression) {
             InExpression in = (InExpression) condition;
-            ((ExpressionList) in.getRightItemsList()).getExpressions().add(plaintext);
+            ((ExpressionList) in.getRightItemsList()).getExpressions().add(target);
         } else {
             ComparisonOperator comparison = (ComparisonOperator) condition;
             Expression left = comparison.getLeftExpression(), right = comparison.getRightExpression();
             Column _column = left instanceof Column ? (Column) left : (Column) right;
-            InExpression in = new InExpression(_column, new ExpressionList(parameter, plaintext));
+            InExpression in = new InExpression(_column, new ExpressionList(parameter, target));
             in.setNot(condition instanceof NotEqualsTo);
             changing.accept(in);
         }

+ 1 - 1
framework-database/src/main/java/com/chelvc/framework/database/sql/CallableStringDecrypter.java

@@ -29,7 +29,7 @@ public class CallableStringDecrypter extends CallableStatementWrapper {
     private <T> T handle(T original) {
         if (original instanceof String) {
             // 字符串数据解密
-            return (T) AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), (String) original);
+            return (T) AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), (String) original, true);
         }
         return original;
     }

+ 1 - 1
framework-database/src/main/java/com/chelvc/framework/database/sql/ResultStringDecrypter.java

@@ -29,7 +29,7 @@ public class ResultStringDecrypter extends ResultSetWrapper {
     private <T> T handle(T original) {
         if (original instanceof String) {
             // 字符串数据解密
-            return (T) AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), (String) original);
+            return (T) AESUtils.decrypt(DatabaseContextHolder.getDecrypter(), (String) original, true);
         }
         return original;
     }

+ 1 - 1
framework-database/src/main/java/com/chelvc/framework/database/sql/WriteStringEncryptor.java

@@ -56,7 +56,7 @@ public class WriteStringEncryptor extends PreparedStatementWrapper {
     @Override
     public void setString(int parameterIndex, String x) throws SQLException {
         if (DatabaseContextHolder.isSensitiveWritable() && x != null) {
-            x = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), x);
+            x = AESUtils.encrypt(DatabaseContextHolder.getEncryptor(), x, true);
         }
         super.setString(parameterIndex, x);
     }

+ 0 - 1
framework-export/src/main/java/com/chelvc/framework/export/support/DefaultExportHandler.java

@@ -127,7 +127,6 @@ public class DefaultExportHandler implements ExportHandler, ApplicationListener<
     public void onApplicationEvent(ApplicationStartedEvent event) {
         // 初始化导出文件清理线程
         ThreadUtils.run(() -> {
-            log.info("Export file cleaner running...");
             while (!Thread.currentThread().isInterrupted()) {
                 try {
                     RedisContextHolder.tryLockAround("exporting:clear:lock", (Executor) this::clear);

+ 21 - 0
framework-oauth/src/main/java/com/chelvc/framework/oauth/config/OAuthProperties.java

@@ -22,8 +22,29 @@ public class OAuthProperties {
      */
     private String secret;
 
+    /**
+     * 是否区分应用范围
+     */
+    private boolean scoped = true;
+
     /**
      * 忽略地址列表
      */
     private List<String> ignores = Collections.emptyList();
+
+    /**
+     * Redis配置
+     */
+    private final Redis redis = new Redis();
+
+    /**
+     * Redis配置信息
+     */
+    @Data
+    public static class Redis {
+        /**
+         * 是否启用基于Redis的令牌校验
+         */
+        private boolean enabled = true;
+    }
 }

+ 10 - 2
framework-oauth/src/main/java/com/chelvc/framework/oauth/token/RedisTokenValidator.java

@@ -9,10 +9,14 @@ import com.chelvc.framework.base.context.ApplicationContextHolder;
 import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
+import com.chelvc.framework.oauth.config.OAuthProperties;
 import com.chelvc.framework.oauth.context.OAuthContextHolder;
 import com.chelvc.framework.redis.context.RedisContextHolder;
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
@@ -28,7 +32,11 @@ import org.springframework.stereotype.Component;
 @Slf4j
 @Component
 @ConditionalOnClass(RedisContextHolder.class)
+@ConditionalOnProperty(value = "oauth.redis.enabled", havingValue = "true")
+@RequiredArgsConstructor(onConstructor = @__(@Autowired))
 public class RedisTokenValidator implements TokenValidator {
+    private final OAuthProperties properties;
+
     @Override
     public OAuth2TokenValidatorResult validate(Jwt jwt) {
         // 基于Redis令牌有效性校验
@@ -41,7 +49,7 @@ public class RedisTokenValidator implements TokenValidator {
         try {
             values = RedisContextHolder.getDefaultTemplate().opsForHash().multiGet(key, fields);
         } catch (Exception e) {
-            log.warn("Redis token validate failed: {}", e.getMessage());
+            log.warn("Get token from redis failed: {}", e.getMessage());
             return OAuth2TokenValidatorResult.success();
         }
         String scope = ObjectUtils.size(values) > 0 ? (String) StringUtils.ifEmpty(values.get(0), (String) null) : null;
@@ -55,7 +63,7 @@ public class RedisTokenValidator implements TokenValidator {
             throw new OAuth2AuthenticationException(new OAuth2Error(
                     "TOKEN_CHANGED", ApplicationContextHolder.getMessage("Token.Changed"), null
             ));
-        } else if (!Objects.equals(scope, OAuthContextHolder.getScope(jwt))) {
+        } else if (this.properties.isScoped() && !Objects.equals(scope, OAuthContextHolder.getScope(jwt))) {
             // 判断应用范围是否相同,如果不同则表示应用范围已被重置,需要刷新令牌
             String arg = StringUtils.isEmpty(scope) ? StringUtils.EMPTY :
                     ApplicationContextHolder.getMessage(scope);

+ 16 - 0
framework-security/src/main/java/com/chelvc/framework/security/config/SecurityConfigurer.java

@@ -5,6 +5,8 @@ import javax.servlet.Filter;
 
 import com.chelvc.framework.base.interceptor.BufferedRequestInterceptor;
 import com.chelvc.framework.common.annotation.Sensitive;
+import com.chelvc.framework.security.context.DefaultSecurityCryptoContext;
+import com.chelvc.framework.security.context.SecurityCryptoContext;
 import com.chelvc.framework.security.interceptor.MethodSecurityExpression;
 import com.chelvc.framework.security.serializer.SensitiveArraySerializer;
 import com.chelvc.framework.security.serializer.SensitiveListSerializer;
@@ -15,8 +17,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.introspect.Annotated;
 import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
 import com.fasterxml.jackson.databind.type.CollectionType;
+import lombok.RequiredArgsConstructor;
 import org.aopalliance.intercept.MethodInvocation;
 import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.ApplicationContext;
@@ -42,7 +46,10 @@ import org.springframework.web.filter.CorsFilter;
  */
 @Configuration
 @EnableGlobalMethodSecurity(prePostEnabled = true)
+@RequiredArgsConstructor(onConstructor = @__(@Autowired))
 public class SecurityConfigurer extends GlobalMethodSecurityConfiguration implements ApplicationContextAware {
+    private final SecurityProperties properties;
+
     @Override
     protected MethodSecurityExpressionHandler createExpressionHandler() {
         return new DefaultMethodSecurityExpressionHandler() {
@@ -63,6 +70,9 @@ public class SecurityConfigurer extends GlobalMethodSecurityConfiguration implem
     @Override
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
         // 设置敏感数据注解JSON序列化处理器
+        if (!this.properties.getSensitive().isEnabled()) {
+            return;
+        }
         ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class);
         mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
             @Override
@@ -88,6 +98,12 @@ public class SecurityConfigurer extends GlobalMethodSecurityConfiguration implem
         });
     }
 
+    @Bean
+    @ConditionalOnMissingBean(SecurityCryptoContext.class)
+    public SecurityCryptoContext securityCryptoContext() {
+        return new DefaultSecurityCryptoContext(this.properties);
+    }
+
     @Bean
     public FilterRegistrationBean<Filter> crossDomainAccessRegistration() {
         FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();

+ 5 - 20
framework-security/src/main/java/com/chelvc/framework/security/config/SecurityProperties.java

@@ -19,39 +19,24 @@ public class SecurityProperties {
      */
     private String secret;
 
-    /**
-     * 加解密初始向量
-     */
-    private String iv;
-
     /**
      * 请求时长(毫秒)
      */
     private long duration = 60 * 1000;
 
     /**
-     * 密文标记配置
+     * 敏感字段配置
      */
-    private final Mark mark = new Mark();
+    private final Sensitive sensitive = new Sensitive();
 
     /**
-     * 密文标记信息
+     * 敏感字段配置,@Sensitive注解
      */
     @Data
-    public static class Mark {
+    public static class Sensitive {
         /**
-         * 是否开启密文标记
+         * 是否开启敏感字段加
          */
         private boolean enabled;
-
-        /**
-         * 标记前缀
-         */
-        private String prefix = "ENC(";
-
-        /**
-         * 标记后缀
-         */
-        private String suffix = ")";
     }
 }

+ 17 - 0
framework-security/src/main/java/com/chelvc/framework/security/context/DefaultSecurityCryptoContext.java

@@ -0,0 +1,17 @@
+package com.chelvc.framework.security.context;
+
+import com.chelvc.framework.common.crypto.AESCipherFactory;
+import com.chelvc.framework.security.config.SecurityProperties;
+import lombok.NonNull;
+
+/**
+ * 信息安全数据加解密上下文默认实现
+ *
+ * @author Woody
+ * @date 2024/6/25
+ */
+public class DefaultSecurityCryptoContext extends AESCipherFactory implements SecurityCryptoContext {
+    public DefaultSecurityCryptoContext(@NonNull SecurityProperties properties) {
+        super(properties.getSecret());
+    }
+}

+ 13 - 105
framework-security/src/main/java/com/chelvc/framework/security/context/SecurityContextHolder.java

@@ -1,20 +1,14 @@
 package com.chelvc.framework.security.context;
 
-import java.util.List;
 import java.util.Set;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import javax.crypto.Cipher;
 
 import com.chelvc.framework.base.context.ApplicationContextHolder;
 import com.chelvc.framework.base.context.Session;
 import com.chelvc.framework.base.context.SessionContextHolder;
-import com.chelvc.framework.common.util.AESUtils;
-import com.chelvc.framework.common.util.AssertUtils;
 import com.chelvc.framework.common.util.CodecUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
-import com.chelvc.framework.common.util.StringUtils;
-import com.chelvc.framework.security.config.SecurityProperties;
 import lombok.NonNull;
 
 /**
@@ -30,43 +24,27 @@ public final class SecurityContextHolder {
     public static final String DEFAULT_PERMISSION_GROUP = "DEFAULT";
 
     /**
-     * 配置属性
+     * 数据加解密上下文
      */
-    private static SecurityProperties PROPERTIES;
+    private static SecurityCryptoContext CRYPTO_CONTEXT;
 
     private SecurityContextHolder() {
     }
 
     /**
-     * 获取配置属性
+     * 获取数据加解密上下文
      *
-     * @return 配置属性
+     * @return 数据加解密上下文实例
      */
-    private static SecurityProperties getProperties() {
-        if (PROPERTIES == null) {
-            synchronized (SecurityProperties.class) {
-                if (PROPERTIES == null) {
-                    PROPERTIES = ApplicationContextHolder.getBean(SecurityProperties.class);
+    public static SecurityCryptoContext getCryptoContext() {
+        if (CRYPTO_CONTEXT == null) {
+            synchronized (SecurityCryptoContext.class) {
+                if (CRYPTO_CONTEXT == null) {
+                    CRYPTO_CONTEXT = ApplicationContextHolder.getBean(SecurityCryptoContext.class);
                 }
             }
         }
-        return PROPERTIES;
-    }
-
-    /**
-     * 获取密码处理器
-     *
-     * @param mode 加解密模式
-     * @return 密码处理器实例
-     */
-    private static Cipher getCipher(int mode) {
-        SecurityProperties properties = getProperties();
-        String secret = AssertUtils.nonempty(properties.getSecret(), () -> "Cipher secret is missing");
-        String iv = AssertUtils.nonempty(properties.getIv(), () -> "Cipher iv is missing");
-        return AESUtils.lookupCipher(
-                AESUtils.CBC_PKCS5PADDING + secret, mode,
-                () -> AESUtils.getCipher(AESUtils.CBC_PKCS5PADDING, mode, secret, iv)
-        );
+        return CRYPTO_CONTEXT;
     }
 
     /**
@@ -75,7 +53,7 @@ public final class SecurityContextHolder {
      * @return 加密处理器
      */
     public static Cipher getEncryptor() {
-        return getCipher(Cipher.ENCRYPT_MODE);
+        return getCryptoContext().getEncryptor();
     }
 
     /**
@@ -84,66 +62,7 @@ public final class SecurityContextHolder {
      * @return 解密处理器
      */
     public static Cipher getDecrypter() {
-        return getCipher(Cipher.DECRYPT_MODE);
-    }
-
-    /**
-     * 标记敏感信息
-     *
-     * @param ciphertext 敏感信息密文
-     * @return 敏感信息
-     */
-    public static String mark(String ciphertext) {
-        SecurityProperties.Mark mark = getProperties().getMark();
-        if (!mark.isEnabled() || StringUtils.isEmpty(ciphertext)) {
-            return ciphertext;
-        }
-        String prefix = ObjectUtils.ifNull(mark.getPrefix(), StringUtils.EMPTY);
-        String suffix = ObjectUtils.ifNull(mark.getSuffix(), StringUtils.EMPTY);
-        return prefix + ciphertext + suffix;
-    }
-
-    /**
-     * 标记敏感信息
-     *
-     * @param ciphertexts 敏感信息密文数组
-     * @return 敏感信息数组
-     */
-    public static String[] mark(String... ciphertexts) {
-        if (ObjectUtils.isEmpty(ciphertexts)) {
-            return ciphertexts;
-        }
-        String[] marks = new String[ciphertexts.length];
-        for (int i = 0; i < ciphertexts.length; i++) {
-            marks[i] = mark(ciphertexts[i]);
-        }
-        return marks;
-    }
-
-    /**
-     * 标记敏感信息
-     *
-     * @param ciphertexts 敏感信息密文集合
-     * @return 敏感信息集合
-     */
-    public static Set<String> mark(Set<String> ciphertexts) {
-        if (ObjectUtils.isEmpty(ciphertexts)) {
-            return ciphertexts;
-        }
-        return ciphertexts.stream().map(SecurityContextHolder::mark).collect(Collectors.toSet());
-    }
-
-    /**
-     * 标记敏感信息
-     *
-     * @param ciphertexts 敏感信息密文列表
-     * @return 敏感信息列表
-     */
-    public static List<String> mark(List<String> ciphertexts) {
-        if (ObjectUtils.isEmpty(ciphertexts)) {
-            return ciphertexts;
-        }
-        return ciphertexts.stream().map(SecurityContextHolder::mark).collect(Collectors.toList());
+        return getCryptoContext().getDecrypter();
     }
 
     /**
@@ -154,23 +73,12 @@ public final class SecurityContextHolder {
      * @return 签名信息
      */
     public static String sign(@NonNull Session session, String payload) {
-        String secret = AssertUtils.nonempty(getProperties().getSecret(), () -> "Security secret is missing");
+        String secret = getCryptoContext().getSecret();
         String plaintext = secret + session.getPlatform() + session.getTerminal() + session.getVersion() +
                 session.getTimestamp() + payload;
         return CodecUtils.md5(plaintext);
     }
 
-    /**
-     * 校验请求时间戳是否有效
-     *
-     * @param timestamp 时间戳
-     * @return true/false
-     */
-    public static boolean validateTimestamp(long timestamp) {
-        long duration = getProperties().getDuration();
-        return duration < 1 || Math.abs(System.currentTimeMillis() - timestamp) <= duration;
-    }
-
     /**
      * 判断当前用户是否拥有任意权限
      *

+ 12 - 0
framework-security/src/main/java/com/chelvc/framework/security/context/SecurityCryptoContext.java

@@ -0,0 +1,12 @@
+package com.chelvc.framework.security.context;
+
+import com.chelvc.framework.common.crypto.CipherFactory;
+
+/**
+ * 信息安全数据加解密上下文接口
+ *
+ * @author Woody
+ * @date 2024/6/25
+ */
+public interface SecurityCryptoContext extends CipherFactory {
+}

+ 70 - 31
framework-security/src/main/java/com/chelvc/framework/security/interceptor/ControllerCryptoInterceptor.java

@@ -4,12 +4,11 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Type;
-import javax.crypto.Cipher;
 
 import com.chelvc.framework.base.context.ApplicationContextHolder;
 import com.chelvc.framework.base.context.JacksonContextHolder;
+import com.chelvc.framework.base.context.Result;
 import com.chelvc.framework.base.interceptor.BufferedRequestWrapper;
-import com.chelvc.framework.base.interceptor.ResponseHandler;
 import com.chelvc.framework.common.exception.FrameworkException;
 import com.chelvc.framework.common.util.AESUtils;
 import com.chelvc.framework.common.util.FileUtils;
@@ -18,15 +17,17 @@ import com.chelvc.framework.security.context.SecurityContextHolder;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.codec.binary.Base64;
 import org.springframework.core.MethodParameter;
-import org.springframework.core.Ordered;
 import org.springframework.core.annotation.Order;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpInputMessage;
 import org.springframework.http.HttpStatus;
+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.web.bind.annotation.ControllerAdvice;
-import org.springframework.web.context.request.NativeWebRequest;
 import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
 
 /**
  * 接口参数加解密拦截器
@@ -35,9 +36,9 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAd
  * @date 2024/1/30
  */
 @Slf4j
+@Order(2)
 @ControllerAdvice
-@Order(Ordered.LOWEST_PRECEDENCE - 1)
-public class ControllerCryptoInterceptor extends RequestBodyAdviceAdapter implements ResponseHandler {
+public class ControllerCryptoInterceptor extends RequestBodyAdviceAdapter implements ResponseBodyAdvice<Object> {
     /**
      * 获取目标方法@Crypto注解
      *
@@ -52,6 +53,49 @@ public class ControllerCryptoInterceptor extends RequestBodyAdviceAdapter implem
         return annotation;
     }
 
+    /**
+     * 数据加密
+     *
+     * @param plaintext 数据明文
+     * @return 数据密文
+     */
+    protected String encrypt(Object plaintext) {
+        String json = JacksonContextHolder.serialize(plaintext);
+        return AESUtils.encrypt(SecurityContextHolder.getEncryptor(), json);
+    }
+
+    /**
+     * 数据解密
+     *
+     * @param ciphertext 数据密文
+     * @return 数据明文
+     */
+    protected byte[] decrypt(byte[] ciphertext) {
+        return AESUtils.codec(SecurityContextHolder.getDecrypter(), ciphertext);
+    }
+
+    /**
+     * 数据解密
+     *
+     * @param message 原始消息
+     * @return 明文消息
+     * @throws IOException I/O异常
+     */
+    protected HttpInputMessage decrypt(HttpInputMessage message) throws IOException {
+        byte[] plaintext = this.decrypt(this.getCiphertext(message));
+        return new HttpInputMessage() {
+            @Override
+            public HttpHeaders getHeaders() {
+                return message.getHeaders();
+            }
+
+            @Override
+            public InputStream getBody() throws IOException {
+                return new ByteArrayInputStream(plaintext);
+            }
+        };
+    }
+
     /**
      * 获取消息密文
      *
@@ -59,7 +103,7 @@ public class ControllerCryptoInterceptor extends RequestBodyAdviceAdapter implem
      * @return 密文字节数组
      * @throws IOException I/O异常
      */
-    private byte[] getCiphertext(HttpInputMessage message) throws IOException {
+    protected byte[] getCiphertext(HttpInputMessage message) throws IOException {
         InputStream input = message.getBody();
         if (input instanceof BufferedRequestWrapper.BufferedServletInputStream) {
             return ((BufferedRequestWrapper.BufferedServletInputStream) input).decodeBase64();
@@ -67,12 +111,6 @@ public class ControllerCryptoInterceptor extends RequestBodyAdviceAdapter implem
         return Base64.decodeBase64(FileUtils.getBytes(input));
     }
 
-    @Override
-    public boolean supports(MethodParameter method) {
-        Crypto annotation = this.getCryptoAnnotation(method);
-        return annotation != null && annotation.output();
-    }
-
     @Override
     public boolean supports(MethodParameter method, Type type, Class<? extends HttpMessageConverter<?>> clazz) {
         Crypto annotation = this.getCryptoAnnotation(method);
@@ -82,34 +120,35 @@ public class ControllerCryptoInterceptor extends RequestBodyAdviceAdapter implem
     @Override
     public HttpInputMessage beforeBodyRead(HttpInputMessage message, MethodParameter method, Type type,
                                            Class<? extends HttpMessageConverter<?>> clazz) throws IOException {
-        Cipher cipher = SecurityContextHolder.getDecrypter();
-        byte[] plaintext, ciphertext = this.getCiphertext(message);
         try {
-            plaintext = AESUtils.codec(cipher, ciphertext);
+            return this.decrypt(message);
         } catch (Exception e) {
             log.warn("Request parameter decrypt failed: {}", e.getMessage());
             throw new FrameworkException(HttpStatus.BAD_REQUEST.name(), null,
                     ApplicationContextHolder.getMessage("Request.Invalid"));
         }
-        return new HttpInputMessage() {
-            @Override
-            public HttpHeaders getHeaders() {
-                return message.getHeaders();
-            }
+    }
 
-            @Override
-            public InputStream getBody() throws IOException {
-                return new ByteArrayInputStream(plaintext);
-            }
-        };
+    @Override
+    public boolean supports(MethodParameter method, Class<? extends HttpMessageConverter<?>> clazz) {
+        Crypto annotation = this.getCryptoAnnotation(method);
+        return annotation != null && annotation.output();
     }
 
     @Override
-    public Object handle(NativeWebRequest request, MethodParameter method, Object value) {
-        if (value != null) {
-            Cipher cipher = SecurityContextHolder.getEncryptor();
-            value = AESUtils.encrypt(cipher, JacksonContextHolder.serialize(value));
+    public Object beforeBodyWrite(Object body, MethodParameter method, MediaType media,
+                                  Class<? extends HttpMessageConverter<?>> clazz,
+                                  ServerHttpRequest request, ServerHttpResponse response) {
+        if (body == null) {
+            return null;
+        } else if (body instanceof Result) {
+            Result<?> result = (Result<?>) body;
+            if (result.getData() == null) {
+                return result;
+            }
+            String ciphertext = this.encrypt(result.getData());
+            return Result.of(result.getCode(), ciphertext, result.getMessage());
         }
-        return value;
+        return this.encrypt(body);
     }
 }

+ 18 - 1
framework-security/src/main/java/com/chelvc/framework/security/interceptor/SecurityValidateInterceptor.java

@@ -12,8 +12,11 @@ import com.chelvc.framework.base.util.HttpUtils;
 import com.chelvc.framework.common.exception.FrameworkException;
 import com.chelvc.framework.common.util.StringUtils;
 import com.chelvc.framework.security.annotation.Security;
+import com.chelvc.framework.security.config.SecurityProperties;
 import com.chelvc.framework.security.context.SecurityContextHolder;
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.core.Ordered;
 import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Component;
@@ -30,12 +33,15 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  */
 @Slf4j
 @Component
+@RequiredArgsConstructor(onConstructor = @__(@Autowired))
 public class SecurityValidateInterceptor implements HandlerInterceptor, WebMvcConfigurer {
     /**
      * 签名信息请求头
      */
     private static final String HEADER_SIGNATURE = "signature";
 
+    private final SecurityProperties properties;
+
     /**
      * 获取接口安全注解实例
      *
@@ -54,6 +60,17 @@ public class SecurityValidateInterceptor implements HandlerInterceptor, WebMvcCo
         return annotation;
     }
 
+    /**
+     * 校验请求时间戳是否有效
+     *
+     * @param timestamp 时间戳
+     * @return true/false
+     */
+    protected boolean validateTimestamp(long timestamp) {
+        long duration = this.properties.getDuration();
+        return duration < 1 || Math.abs(System.currentTimeMillis() - timestamp) <= duration;
+    }
+
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
             throws Exception {
@@ -73,7 +90,7 @@ public class SecurityValidateInterceptor implements HandlerInterceptor, WebMvcCo
             }
 
             // 请求时间戳校验
-            if (!SecurityContextHolder.validateTimestamp(session.getTimestamp())) {
+            if (!this.validateTimestamp(session.getTimestamp())) {
                 throw new FrameworkException(HttpStatus.BAD_REQUEST.name(), null,
                         ApplicationContextHolder.getMessage("Time.Deviated"));
             }

+ 53 - 28
framework-security/src/main/java/com/chelvc/framework/security/interceptor/SensitiveResponseInterceptor.java

@@ -2,21 +2,25 @@ package com.chelvc.framework.security.interceptor;
 
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
+import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
-import javax.crypto.Cipher;
 
-import com.chelvc.framework.base.interceptor.ResponseHandler;
+import com.chelvc.framework.base.context.Result;
 import com.chelvc.framework.common.annotation.Sensitive;
 import com.chelvc.framework.common.util.AESUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.security.context.SecurityContextHolder;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.core.MethodParameter;
-import org.springframework.core.Ordered;
 import org.springframework.core.annotation.Order;
-import org.springframework.stereotype.Component;
-import org.springframework.web.context.request.NativeWebRequest;
+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.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
 
 /**
  * 敏感数据响应拦截器实现
@@ -24,36 +28,57 @@ import org.springframework.web.context.request.NativeWebRequest;
  * @author Woody
  * @date 2024/6/22
  */
-@Component
-@Order(Ordered.LOWEST_PRECEDENCE - 2)
-public class SensitiveResponseInterceptor implements ResponseHandler {
+@Order(1)
+@ControllerAdvice
+@ConditionalOnProperty(value = "security.sensitive.enabled", havingValue = "true")
+public class SensitiveResponseInterceptor implements ResponseBodyAdvice<Object> {
+    /**
+     * 敏感数据加密
+     *
+     * @param method 请求方法
+     * @param value  结果明文
+     * @return 结果密文
+     */
+    @SuppressWarnings("unchecked")
+    protected Object encrypt(MethodParameter method, Object value) {
+        if (value instanceof String) {
+            return AESUtils.encrypt(SecurityContextHolder.getEncryptor(), (String) value);
+        } else if (value instanceof String[] && ObjectUtils.notBlank(value)) {
+            return AESUtils.encrypt(SecurityContextHolder.getEncryptor(), (String[]) value);
+        } else if (value instanceof Collection && ObjectUtils.notBlank(value)) {
+            Type type = Objects.requireNonNull(method.getMethod()).getGenericReturnType();
+            if (type instanceof ParameterizedType) {
+                Type raw = ((ParameterizedType) type).getRawType();
+                Type arg = ((ParameterizedType) type).getActualTypeArguments()[0];
+                if (raw == Set.class && arg == String.class) {
+                    return AESUtils.encrypt(SecurityContextHolder.getEncryptor(), (Set<String>) value);
+                } else if (raw == List.class && arg == String.class) {
+                    return AESUtils.encrypt(SecurityContextHolder.getEncryptor(), (List<String>) value);
+                }
+            }
+        }
+        return value;
+    }
+
     @Override
-    public boolean supports(MethodParameter method) {
+    public boolean supports(MethodParameter method, Class<? extends HttpMessageConverter<?>> clazz) {
         return Objects.nonNull(method.getMethod()) && method.hasMethodAnnotation(Sensitive.class);
     }
 
     @Override
-    @SuppressWarnings("unchecked")
-    public Object handle(NativeWebRequest request, MethodParameter method, Object value) {
-        if (value == null) {
+    public Object beforeBodyWrite(Object body, MethodParameter method, MediaType media,
+                                  Class<? extends HttpMessageConverter<?>> clazz,
+                                  ServerHttpRequest request, ServerHttpResponse response) {
+        if (body == null) {
             return null;
-        }
-        Cipher cipher = SecurityContextHolder.getEncryptor();
-        Type type = Objects.requireNonNull(method.getMethod()).getGenericReturnType();
-        if (type == String.class) {
-            value = SecurityContextHolder.mark(AESUtils.encrypt(cipher, (String) value));
-        } else if (type instanceof ParameterizedType && ObjectUtils.notBlank(value)) {
-            Type raw = ((ParameterizedType) type).getRawType();
-            Type arg = ((ParameterizedType) type).getActualTypeArguments()[0];
-            if (raw == Set.class && arg == String.class) {
-                value = SecurityContextHolder.mark(AESUtils.encrypt(cipher, (Set<String>) value));
-            } else if (raw == List.class && arg == String.class) {
-                value = SecurityContextHolder.mark(AESUtils.encrypt(cipher, (List<String>) value));
+        } else if (body instanceof Result) {
+            Result<?> result = (Result<?>) body;
+            if (result.getData() == null) {
+                return result;
             }
-        } else if (type instanceof Class && ((Class<?>) type).isArray()
-                && ((Class<?>) type).getComponentType() == String.class && ObjectUtils.notBlank(value)) {
-            value = SecurityContextHolder.mark(AESUtils.encrypt(cipher, (String[]) value));
+            Object ciphertext = this.encrypt(method, result.getData());
+            return Result.of(result.getCode(), ciphertext, result.getMessage());
         }
-        return value;
+        return this.encrypt(method, body);
     }
 }

+ 1 - 3
framework-security/src/main/java/com/chelvc/framework/security/serializer/SensitiveArraySerializer.java

@@ -1,7 +1,6 @@
 package com.chelvc.framework.security.serializer;
 
 import java.io.IOException;
-import javax.crypto.Cipher;
 
 import com.chelvc.framework.common.util.AESUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
@@ -21,8 +20,7 @@ public class SensitiveArraySerializer extends JsonSerializer<String[]> {
     public void serialize(String[] value, JsonGenerator generator, SerializerProvider provider)
             throws IOException {
         if (ObjectUtils.notEmpty(value)) {
-            Cipher cipher = SecurityContextHolder.getEncryptor();
-            value = SecurityContextHolder.mark(AESUtils.encrypt(cipher, value));
+            value = AESUtils.encrypt(SecurityContextHolder.getEncryptor(), value);
         }
         generator.writeObject(value);
     }

+ 1 - 3
framework-security/src/main/java/com/chelvc/framework/security/serializer/SensitiveListSerializer.java

@@ -2,7 +2,6 @@ package com.chelvc.framework.security.serializer;
 
 import java.io.IOException;
 import java.util.List;
-import javax.crypto.Cipher;
 
 import com.chelvc.framework.common.util.AESUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
@@ -22,8 +21,7 @@ public class SensitiveListSerializer extends JsonSerializer<List<String>> {
     public void serialize(List<String> value, JsonGenerator generator, SerializerProvider provider)
             throws IOException {
         if (ObjectUtils.notEmpty(value)) {
-            Cipher cipher = SecurityContextHolder.getEncryptor();
-            value = SecurityContextHolder.mark(AESUtils.encrypt(cipher, value));
+            value = AESUtils.encrypt(SecurityContextHolder.getEncryptor(), value);
         }
         generator.writeObject(value);
     }

+ 1 - 3
framework-security/src/main/java/com/chelvc/framework/security/serializer/SensitiveSerializer.java

@@ -1,7 +1,6 @@
 package com.chelvc.framework.security.serializer;
 
 import java.io.IOException;
-import javax.crypto.Cipher;
 
 import com.chelvc.framework.common.util.AESUtils;
 import com.chelvc.framework.security.context.SecurityContextHolder;
@@ -19,8 +18,7 @@ public class SensitiveSerializer extends JsonSerializer<String> {
     @Override
     public void serialize(String value, JsonGenerator generator, SerializerProvider provider) throws IOException {
         if (value != null) {
-            Cipher cipher = SecurityContextHolder.getEncryptor();
-            value = SecurityContextHolder.mark(AESUtils.encrypt(cipher, value));
+            value = AESUtils.encrypt(SecurityContextHolder.getEncryptor(), value);
         }
         generator.writeString(value);
     }

+ 1 - 3
framework-security/src/main/java/com/chelvc/framework/security/serializer/SensitiveSetSerializer.java

@@ -2,7 +2,6 @@ package com.chelvc.framework.security.serializer;
 
 import java.io.IOException;
 import java.util.Set;
-import javax.crypto.Cipher;
 
 import com.chelvc.framework.common.util.AESUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
@@ -22,8 +21,7 @@ public class SensitiveSetSerializer extends JsonSerializer<Set<String>> {
     public void serialize(Set<String> value, JsonGenerator generator, SerializerProvider provider)
             throws IOException {
         if (ObjectUtils.notEmpty(value)) {
-            Cipher cipher = SecurityContextHolder.getEncryptor();
-            value = SecurityContextHolder.mark(AESUtils.encrypt(cipher, value));
+            value = AESUtils.encrypt(SecurityContextHolder.getEncryptor(), value);
         }
         generator.writeObject(value);
     }