diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java index 38b22f388..559002335 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java @@ -9,6 +9,8 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.web.servlet.FilterRegistration; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; /** * api 解密自动配置 @@ -27,8 +29,10 @@ public class ApiDecryptAutoConfiguration { order = FilterRegistrationBean.HIGHEST_PRECEDENCE, dispatcherTypes = DispatcherType.REQUEST ) - public CryptoFilter cryptoFilter(ApiDecryptProperties properties) { - return new CryptoFilter(properties); + public CryptoFilter cryptoFilter(ApiDecryptProperties properties, + RequestMappingHandlerMapping requestMappingHandlerMapping, + HandlerExceptionResolver handlerExceptionResolver) { + return new CryptoFilter(properties, requestMappingHandlerMapping, handlerExceptionResolver); } } diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java index fbc4e52de..dfe1915d1 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java @@ -3,7 +3,11 @@ package org.dromara.common.encrypt.config; import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties; import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.encrypt.core.EncryptContextFactory; +import org.dromara.common.encrypt.core.EncryptedFieldProcessor; import org.dromara.common.encrypt.core.EncryptorManager; +import org.dromara.common.encrypt.enums.AlgorithmType; import org.dromara.common.encrypt.interceptor.MybatisDecryptInterceptor; import org.dromara.common.encrypt.interceptor.MybatisEncryptInterceptor; import org.dromara.common.encrypt.properties.EncryptorProperties; @@ -16,8 +20,7 @@ import org.springframework.context.annotation.Bean; /** * 加解密配置 * - * @author 老马 - * @version 4.6.0 + * @author Lion Li */ @AutoConfiguration(after = MybatisPlusAutoConfiguration.class) @EnableConfigurationProperties(EncryptorProperties.class) @@ -30,17 +33,42 @@ public class EncryptorAutoConfiguration { @Bean public EncryptorManager encryptorManager(MybatisPlusProperties mybatisPlusProperties) { + validateEncryptorProperties(properties); return new EncryptorManager(mybatisPlusProperties.getTypeAliasesPackage()); } @Bean - public MybatisEncryptInterceptor mybatisEncryptInterceptor(EncryptorManager encryptorManager) { - return new MybatisEncryptInterceptor(encryptorManager, properties); + public EncryptContextFactory encryptContextFactory() { + return new EncryptContextFactory(properties); } @Bean - public MybatisDecryptInterceptor mybatisDecryptInterceptor(EncryptorManager encryptorManager) { - return new MybatisDecryptInterceptor(encryptorManager, properties); + public EncryptedFieldProcessor encryptedFieldProcessor(EncryptorManager encryptorManager, EncryptContextFactory encryptContextFactory) { + return new EncryptedFieldProcessor(encryptorManager, encryptContextFactory); + } + + @Bean + public MybatisEncryptInterceptor mybatisEncryptInterceptor(EncryptedFieldProcessor encryptedFieldProcessor) { + return new MybatisEncryptInterceptor(encryptedFieldProcessor); + } + + @Bean + public MybatisDecryptInterceptor mybatisDecryptInterceptor(EncryptedFieldProcessor encryptedFieldProcessor) { + return new MybatisDecryptInterceptor(encryptedFieldProcessor); + } + + private void validateEncryptorProperties(EncryptorProperties properties) { + AlgorithmType algorithm = properties.getAlgorithm(); + if (algorithm == AlgorithmType.AES || algorithm == AlgorithmType.SM4) { + if (StringUtils.isBlank(properties.getPassword())) { + throw new IllegalArgumentException("mybatis-encryptor.password 不能为空"); + } + } + if (algorithm == AlgorithmType.RSA || algorithm == AlgorithmType.SM2) { + if (StringUtils.isAnyBlank(properties.getPublicKey(), properties.getPrivateKey())) { + throw new IllegalArgumentException("mybatis-encryptor.publicKey 与 privateKey 不能为空"); + } + } } } diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptContextFactory.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptContextFactory.java new file mode 100644 index 000000000..12c864052 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptContextFactory.java @@ -0,0 +1,40 @@ +package org.dromara.common.encrypt.core; + +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.encrypt.annotation.EncryptField; +import org.dromara.common.encrypt.enums.AlgorithmType; +import org.dromara.common.encrypt.enums.EncodeType; +import org.dromara.common.encrypt.properties.EncryptorProperties; + +import java.lang.reflect.Field; + +/** + * 加密上下文工厂。 + * + * @author Lion Li + */ +public class EncryptContextFactory { + + private final EncryptorProperties defaultProperties; + + public EncryptContextFactory(EncryptorProperties defaultProperties) { + this.defaultProperties = defaultProperties; + } + + /** + * 根据字段注解和默认配置创建加密上下文。 + * + * @param field 加密字段 + * @return 加密上下文 + */ + public EncryptContext create(Field field) { + EncryptField encryptField = field.getAnnotation(EncryptField.class); + EncryptContext encryptContext = new EncryptContext(); + encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm()); + encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode()); + encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password()); + encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey()); + encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey()); + return encryptContext; + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptedFieldProcessor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptedFieldProcessor.java new file mode 100644 index 000000000..f8d4f340c --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptedFieldProcessor.java @@ -0,0 +1,109 @@ +package org.dromara.common.encrypt.core; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Field; +import java.util.*; + +/** + * 加密字段处理器。 + * + * @author Lion Li + */ +@Slf4j +public class EncryptedFieldProcessor { + + private final EncryptorManager encryptorManager; + private final EncryptContextFactory contextFactory; + + public EncryptedFieldProcessor(EncryptorManager encryptorManager, EncryptContextFactory contextFactory) { + this.encryptorManager = encryptorManager; + this.contextFactory = contextFactory; + } + + /** + * 加密对象字段,并返回原始字段快照。 + * + * @param sourceObject 待加密对象 + * @return 原始字段快照 + */ + public List encrypt(Object sourceObject) { + List snapshots = new ArrayList<>(); + handle(sourceObject, Collections.newSetFromMap(new IdentityHashMap<>()), (target, field, value) -> { + String encrypt = encryptorManager.encrypt(value, contextFactory.create(field)); + if (!Objects.equals(value, encrypt)) { + snapshots.add(new FieldSnapshot(target, field, value)); + field.set(target, encrypt); + } + }); + return snapshots; + } + + /** + * 解密对象字段。 + * + * @param sourceObject 待解密对象 + */ + public void decrypt(Object sourceObject) { + handle(sourceObject, Collections.newSetFromMap(new IdentityHashMap<>()), (target, field, value) -> + field.set(target, encryptorManager.decrypt(value, contextFactory.create(field)))); + } + + private void handle(Object sourceObject, Set visited, FieldHandler fieldHandler) { + if (ObjectUtil.isNull(sourceObject) || sourceObject instanceof String || visited.contains(sourceObject)) { + return; + } + visited.add(sourceObject); + if (sourceObject instanceof Map map) { + new HashSet<>(map.values()).forEach(value -> handle(value, visited, fieldHandler)); + return; + } + if (sourceObject instanceof Collection collection) { + if (CollUtil.isEmpty(collection)) { + return; + } + collection.forEach(item -> handle(item, visited, fieldHandler)); + return; + } + Set fields = encryptorManager.getFieldCache(sourceObject.getClass()); + if (CollUtil.isEmpty(fields)) { + return; + } + try { + for (Field field : fields) { + String value = Convert.toStr(field.get(sourceObject)); + if (ObjectUtil.isNotNull(value)) { + fieldHandler.handle(sourceObject, field, value); + } + } + } catch (Exception e) { + log.error("处理加密字段时出错", e); + } + } + + @FunctionalInterface + private interface FieldHandler { + + void handle(Object target, Field field, String value) throws IllegalAccessException; + } + + /** + * 字段原始值快照。 + */ + public record FieldSnapshot(Object target, Field field, Object value) { + + /** + * 恢复原始字段值。 + */ + public void restore() { + try { + field.set(target, value); + } catch (IllegalAccessException e) { + log.error("恢复加密字段时出错", e); + } + } + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java index 5e2c7318e..2a8fcfcc4 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java @@ -2,13 +2,13 @@ package org.dromara.common.encrypt.core; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ReflectUtil; -import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.io.Resources; import org.dromara.common.core.constant.Constants; -import org.dromara.common.core.utils.ObjectUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.encrypt.annotation.EncryptField; +import org.dromara.common.encrypt.enums.AlgorithmType; +import org.dromara.common.encrypt.enums.EncodeType; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; @@ -28,17 +28,15 @@ import java.util.stream.Collectors; /** * 加密管理类 * - * @author 老马 - * @version 4.6.0 + * @author Lion Li */ @Slf4j -@NoArgsConstructor public class EncryptorManager { /** * 缓存加密器 */ - Map encryptorMap = new ConcurrentHashMap<>(); + Map encryptorMap = new ConcurrentHashMap<>(); /** * 类加密字段缓存 @@ -59,7 +57,13 @@ public class EncryptorManager { * 获取类加密字段缓存 */ public Set getFieldCache(Class sourceClazz) { - return ObjectUtils.notNullGetter(fieldCache, f -> f.get(sourceClazz)); + if (sourceClazz == null || sourceClazz.isPrimitive() || sourceClazz.isArray() + || sourceClazz.isEnum() || sourceClazz.isAnnotation() + || ClassUtils.isPrimitiveOrWrapper(sourceClazz) + || sourceClazz.getName().startsWith("java.")) { + return Set.of(); + } + return fieldCache.computeIfAbsent(sourceClazz, this::getEncryptFieldSetFromClazz); } /** @@ -68,13 +72,9 @@ public class EncryptorManager { * @param encryptContext 加密执行者需要的相关配置参数 */ public IEncryptor registAndGetEncryptor(EncryptContext encryptContext) { - int key = encryptContext.hashCode(); - if (encryptorMap.containsKey(key)) { - return encryptorMap.get(key); - } - IEncryptor encryptor = ReflectUtil.newInstance(encryptContext.getAlgorithm().getClazz(), encryptContext); - encryptorMap.put(key, encryptor); - return encryptor; + EncryptorCacheKey key = EncryptorCacheKey.of(encryptContext); + return encryptorMap.computeIfAbsent(key, cacheKey -> + ReflectUtil.newInstance(cacheKey.algorithm().getClazz(), encryptContext)); } /** @@ -83,7 +83,7 @@ public class EncryptorManager { * @param encryptContext 加密执行者需要的相关配置参数 */ public void removeEncryptor(EncryptContext encryptContext) { - this.encryptorMap.remove(encryptContext.hashCode()); + this.encryptorMap.remove(EncryptorCacheKey.of(encryptContext)); } /** @@ -120,6 +120,9 @@ public class EncryptorManager { * 通过 typeAliasesPackage 设置的扫描包 扫描缓存实体 */ private void scanEncryptClasses(String typeAliasesPackage) { + if (StringUtils.isBlank(typeAliasesPackage)) { + return; + } PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); String[] packagePatternArray = StringUtils.splitPreserveAllTokens(typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); @@ -149,7 +152,7 @@ public class EncryptorManager { Set fieldSet = new HashSet<>(); // 判断clazz如果是接口,内部类,匿名类就直接返回 if (clazz.isInterface() || clazz.isMemberClass() || clazz.isAnonymousClass()) { - return fieldSet; + return Set.of(); } while (clazz != null) { Field[] fields = clazz.getDeclaredFields(); @@ -165,4 +168,22 @@ public class EncryptorManager { return fieldSet; } + private record EncryptorCacheKey( + AlgorithmType algorithm, + EncodeType encode, + String password, + String publicKey, + String privateKey + ) { + + private static EncryptorCacheKey of(EncryptContext encryptContext) { + return new EncryptorCacheKey( + encryptContext.getAlgorithm(), + encryptContext.getEncode(), + encryptContext.getPassword(), + encryptContext.getPublicKey(), + encryptContext.getPrivateKey()); + } + } + } diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java index 2445541e5..f3eb24ab5 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java @@ -6,7 +6,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.dromara.common.core.constant.HttpStatus; import org.dromara.common.core.exception.ServiceException; -import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.encrypt.annotation.ApiEncrypt; import org.dromara.common.encrypt.properties.ApiDecryptProperties; @@ -27,9 +26,15 @@ import java.io.IOException; */ public class CryptoFilter implements Filter { private final ApiDecryptProperties properties; + private final RequestMappingHandlerMapping requestMappingHandlerMapping; + private final HandlerExceptionResolver handlerExceptionResolver; - public CryptoFilter(ApiDecryptProperties properties) { + public CryptoFilter(ApiDecryptProperties properties, + RequestMappingHandlerMapping requestMappingHandlerMapping, + HandlerExceptionResolver handlerExceptionResolver) { this.properties = properties; + this.requestMappingHandlerMapping = requestMappingHandlerMapping; + this.handlerExceptionResolver = handlerExceptionResolver; EncryptUtils.validateRsaPublicKey(properties.getPublicKey()); EncryptUtils.validateRsaPrivateKey(properties.getPrivateKey()); } @@ -55,8 +60,7 @@ public class CryptoFilter implements Filter { } else { // 是否有注解,有就报错,没有放行 if (ObjectUtil.isNotNull(apiEncrypt)) { - HandlerExceptionResolver exceptionResolver = SpringUtils.getBean("handlerExceptionResolver", HandlerExceptionResolver.class); - exceptionResolver.resolveException( + handlerExceptionResolver.resolveException( servletRequest, servletResponse, null, new ServiceException("没有访问权限,请联系管理员授权", HttpStatus.FORBIDDEN)); return; @@ -75,7 +79,6 @@ public class CryptoFilter implements Filter { ObjectUtil.defaultIfNull(responseWrapper, response)); if (responseFlag) { - servletResponse.reset(); // 对原始内容加密 String encryptContent = responseBodyWrapper.getEncryptContent( servletResponse, properties.getPublicKey(), properties.getHeaderFlag()); @@ -88,10 +91,9 @@ public class CryptoFilter implements Filter { * 获取 ApiEncrypt 注解 */ private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest servletRequest) { - RequestMappingHandlerMapping handlerMapping = SpringUtils.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class); // 获取注解 try { - HandlerExecutionChain mappingHandler = handlerMapping.getHandler(servletRequest); + HandlerExecutionChain mappingHandler = requestMappingHandlerMapping.getHandler(servletRequest); if (ObjectUtil.isNotNull(mappingHandler)) { Object handler = mappingHandler.getHandler(); if (ObjectUtil.isNotNull(handler)) { diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java index 98f4bc7c8..b3afab84f 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java @@ -13,6 +13,7 @@ import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; /** @@ -41,7 +42,8 @@ public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper { @Override public BufferedReader getReader() { - return new BufferedReader(new InputStreamReader(getInputStream())); + Charset charset = Charset.forName(getCharacterEncoding()); + return new BufferedReader(new InputStreamReader(getInputStream(), charset)); } @@ -72,17 +74,17 @@ public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper { @Override public int available() { - return body.length; + return bais.available(); } @Override public boolean isFinished() { - return false; + return bais.available() == 0; } @Override public boolean isReady() { - return false; + return true; } @Override diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java index 78e06ff73..06cb1f096 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java @@ -1,6 +1,5 @@ package org.dromara.common.encrypt.filter; -import cn.hutool.core.util.RandomUtil; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.WriteListener; import jakarta.servlet.http.HttpServletResponse; @@ -8,7 +7,10 @@ import jakarta.servlet.http.HttpServletResponseWrapper; import org.dromara.common.encrypt.utils.EncryptUtils; import java.io.*; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.Base64; /** * 加密响应参数包装类 @@ -17,19 +19,26 @@ import java.nio.charset.StandardCharsets; */ public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper { + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + private final ByteArrayOutputStream byteArrayOutputStream; private final ServletOutputStream servletOutputStream; - private final PrintWriter printWriter; + private PrintWriter printWriter; + private Charset charset; public EncryptResponseBodyWrapper(HttpServletResponse response) throws IOException { super(response); this.byteArrayOutputStream = new ByteArrayOutputStream(); this.servletOutputStream = this.getOutputStream(); - this.printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream)); + this.charset = resolveCharset(response); } @Override public PrintWriter getWriter() { + if (printWriter == null) { + charset = resolveCharset((HttpServletResponse) getResponse()); + printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream, charset)); + } return printWriter; } @@ -48,6 +57,11 @@ public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper { byteArrayOutputStream.reset(); } + @Override + public void resetBuffer() { + byteArrayOutputStream.reset(); + } + public byte[] getResponseData() throws IOException { flushBuffer(); return byteArrayOutputStream.toByteArray(); @@ -55,7 +69,7 @@ public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper { public String getContent() throws IOException { flushBuffer(); - return byteArrayOutputStream.toString(); + return byteArrayOutputStream.toString(charset); } /** @@ -69,7 +83,7 @@ public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper { */ public String getEncryptContent(HttpServletResponse servletResponse, String publicKey, String headerFlag) throws IOException { // 生成秘钥 - String aesPassword = RandomUtil.randomString(32); + String aesPassword = generateAesPassword(); // 秘钥使用 Base64 编码 String encryptAes = EncryptUtils.encryptByBase64(aesPassword); // Rsa 公钥加密 Base64 编码 @@ -78,16 +92,16 @@ public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper { // 设置响应头 // vue版本需要设置 servletResponse.addHeader("Access-Control-Expose-Headers", headerFlag); - servletResponse.setHeader("Access-Control-Allow-Origin", "*"); - servletResponse.setHeader("Access-Control-Allow-Methods", "*"); servletResponse.setHeader(headerFlag, encryptPassword); - servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString()); + servletResponse.setCharacterEncoding(charset.name()); // 获取原始内容 String originalBody = this.getContent(); // 对内容进行加密 - return EncryptUtils.encryptByAes(originalBody, aesPassword); + String encryptContent = EncryptUtils.encryptByAes(originalBody, aesPassword); + servletResponse.setContentLengthLong(encryptContent.getBytes(charset).length); + return encryptContent; } @Override @@ -95,7 +109,7 @@ public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper { return new ServletOutputStream() { @Override public boolean isReady() { - return false; + return true; } @Override @@ -120,4 +134,18 @@ public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper { }; } + private Charset resolveCharset(HttpServletResponse response) { + String characterEncoding = response.getCharacterEncoding(); + if (characterEncoding == null) { + return StandardCharsets.UTF_8; + } + return Charset.forName(characterEncoding); + } + + private String generateAesPassword() { + byte[] bytes = new byte[24]; + SECURE_RANDOM.nextBytes(bytes); + return Base64.getEncoder().encodeToString(bytes); + } + } diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java index 1122669e7..e278fcecd 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java @@ -1,32 +1,18 @@ package org.dromara.common.encrypt.interceptor; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.util.ObjectUtil; import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.resultset.ResultSetHandler; import org.apache.ibatis.plugin.*; -import org.dromara.common.core.utils.StringUtils; -import org.dromara.common.encrypt.annotation.EncryptField; -import org.dromara.common.encrypt.core.EncryptContext; -import org.dromara.common.encrypt.core.EncryptorManager; -import org.dromara.common.encrypt.enums.AlgorithmType; -import org.dromara.common.encrypt.enums.EncodeType; -import org.dromara.common.encrypt.properties.EncryptorProperties; +import org.dromara.common.encrypt.core.EncryptedFieldProcessor; -import java.lang.reflect.Field; import java.sql.Statement; -import java.util.*; +import java.util.Properties; /** * 出参解密拦截器 * - * @author 老马 - * @version 4.6.0 + * @author Lion Li */ -@Slf4j @Intercepts({@Signature( type = ResultSetHandler.class, method = "handleResultSets", @@ -35,91 +21,19 @@ import java.util.*; @AllArgsConstructor public class MybatisDecryptInterceptor implements Interceptor { - private final EncryptorManager encryptorManager; - private final EncryptorProperties defaultProperties; + private final EncryptedFieldProcessor encryptedFieldProcessor; @Override public Object intercept(Invocation invocation) throws Throwable { - // 开始进行参数解密 - ResultSetHandler resultSetHandler = (ResultSetHandler) invocation.getTarget(); - Field parameterHandlerField = resultSetHandler.getClass().getDeclaredField("parameterHandler"); - parameterHandlerField.setAccessible(true); - Object target = parameterHandlerField.get(resultSetHandler); - if (target instanceof ParameterHandler parameterHandler) { - Object parameterObject = parameterHandler.getParameterObject(); - if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) { - this.decryptHandler(parameterObject); - } - } // 获取执行mysql执行结果 Object result = invocation.proceed(); if (result == null) { return null; } - this.decryptHandler(result); + encryptedFieldProcessor.decrypt(result); return result; } - /** - * 解密对象 - * - * @param sourceObject 待加密对象 - */ - private void decryptHandler(Object sourceObject) { - if (ObjectUtil.isNull(sourceObject)) { - return; - } - if (sourceObject instanceof Map map) { - new HashSet<>(map.values()).forEach(this::decryptHandler); - return; - } - if (sourceObject instanceof List list) { - if(CollUtil.isEmpty(list)) { - return; - } - // 判断第一个元素是否含有注解。如果没有直接返回,提高效率 - Object firstItem = list.get(0); - if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) { - return; - } - list.forEach(this::decryptHandler); - return; - } - // 不在缓存中的类,就是没有加密注解的类(当然也有可能是typeAliasesPackage写错) - Set fields = encryptorManager.getFieldCache(sourceObject.getClass()); - if(ObjectUtil.isNull(fields)){ - return; - } - try { - for (Field field : fields) { - field.set(sourceObject, this.decryptField(Convert.toStr(field.get(sourceObject)), field)); - } - } catch (Exception e) { - log.error("处理解密字段时出错", e); - } - } - - /** - * 字段值进行加密。通过字段的批注注册新的加密算法 - * - * @param value 待加密的值 - * @param field 待加密字段 - * @return 加密后结果 - */ - private String decryptField(String value, Field field) { - if (ObjectUtil.isNull(value)) { - return null; - } - EncryptField encryptField = field.getAnnotation(EncryptField.class); - EncryptContext encryptContext = new EncryptContext(); - encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm()); - encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode()); - encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password()); - encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey()); - encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey()); - return this.encryptorManager.decrypt(value, encryptContext); - } - @Override public Object plugin(Object target) { return Plugin.wrap(target, this); diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java index 3525f19a0..7659bd9b7 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java @@ -1,31 +1,20 @@ package org.dromara.common.encrypt.interceptor; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.convert.Convert; import cn.hutool.core.util.ObjectUtil; import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.plugin.*; -import org.dromara.common.core.utils.StringUtils; -import org.dromara.common.encrypt.annotation.EncryptField; -import org.dromara.common.encrypt.core.EncryptContext; -import org.dromara.common.encrypt.core.EncryptorManager; -import org.dromara.common.encrypt.enums.AlgorithmType; -import org.dromara.common.encrypt.enums.EncodeType; -import org.dromara.common.encrypt.properties.EncryptorProperties; +import org.dromara.common.encrypt.core.EncryptedFieldProcessor; -import java.lang.reflect.Field; import java.sql.PreparedStatement; -import java.util.*; +import java.util.List; +import java.util.Properties; /** * 入参加密拦截器 * - * @author 老马 - * @version 4.6.0 + * @author Lion Li */ -@Slf4j @Intercepts({@Signature( type = ParameterHandler.class, method = "setParameters", @@ -34,19 +23,25 @@ import java.util.*; @AllArgsConstructor public class MybatisEncryptInterceptor implements Interceptor { - private final EncryptorManager encryptorManager; - private final EncryptorProperties defaultProperties; + private final EncryptedFieldProcessor encryptedFieldProcessor; @Override public Object intercept(Invocation invocation) throws Throwable { + List snapshots = List.of(); Object target = invocation.getTarget(); if (target instanceof ParameterHandler parameterHandler) { Object parameterObject = parameterHandler.getParameterObject(); if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) { - this.encryptHandler(parameterObject); + snapshots = encryptedFieldProcessor.encrypt(parameterObject); + } + } + try { + return invocation.proceed(); + } finally { + for (EncryptedFieldProcessor.FieldSnapshot snapshot : snapshots) { + snapshot.restore(); } } - return invocation.proceed(); } @Override @@ -54,67 +49,6 @@ public class MybatisEncryptInterceptor implements Interceptor { return Plugin.wrap(target, this); } - /** - * 加密对象 - * - * @param sourceObject 待加密对象 - */ - private void encryptHandler(Object sourceObject) { - if (ObjectUtil.isNull(sourceObject)) { - return; - } - if (sourceObject instanceof Map map) { - new HashSet<>(map.values()).forEach(this::encryptHandler); - return; - } - if (sourceObject instanceof List list) { - if(CollUtil.isEmpty(list)) { - return; - } - // 判断第一个元素是否含有注解。如果没有直接返回,提高效率 - Object firstItem = list.get(0); - if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) { - return; - } - list.forEach(this::encryptHandler); - return; - } - // 不在缓存中的类,就是没有加密注解的类(当然也有可能是typeAliasesPackage写错) - Set fields = encryptorManager.getFieldCache(sourceObject.getClass()); - if(ObjectUtil.isNull(fields)){ - return; - } - try { - for (Field field : fields) { - field.set(sourceObject, this.encryptField(Convert.toStr(field.get(sourceObject)), field)); - } - } catch (Exception e) { - log.error("处理加密字段时出错", e); - } - } - - /** - * 字段值进行加密。通过字段的批注注册新的加密算法 - * - * @param value 待加密的值 - * @param field 待加密字段 - * @return 加密后结果 - */ - private String encryptField(String value, Field field) { - if (ObjectUtil.isNull(value)) { - return null; - } - EncryptField encryptField = field.getAnnotation(EncryptField.class); - EncryptContext encryptContext = new EncryptContext(); - encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm()); - encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode()); - encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password()); - encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey()); - encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey()); - return this.encryptorManager.encrypt(value, encryptContext); - } - - @Override public void setProperties(Properties properties) { } diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java index 6aadb3e03..969efbf2b 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java @@ -19,7 +19,7 @@ public class ApiDecryptProperties { /** * 头部标识 */ - private String headerFlag; + private String headerFlag = "encrypt-key"; /** * 响应加密公钥 diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/EncryptorProperties.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/EncryptorProperties.java index a6886b061..04b9e1aca 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/EncryptorProperties.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/EncryptorProperties.java @@ -23,7 +23,7 @@ public class EncryptorProperties { /** * 默认算法 */ - private AlgorithmType algorithm; + private AlgorithmType algorithm = AlgorithmType.BASE64; /** * 安全秘钥 @@ -43,6 +43,6 @@ public class EncryptorProperties { /** * 编码方式,base64/hex */ - private EncodeType encode; + private EncodeType encode = EncodeType.BASE64; }