From 617cf85209d3a761669a09e07dc7df3229c33294 Mon Sep 17 00:00:00 2001 From: dataprince Date: Fri, 29 Dec 2023 15:19:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E2=80=9Cruoyi-common-encrypt?= =?UTF-8?q?=E2=80=9D=E5=8A=A0=E5=AF=86=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 9 + .../src/main/resources/application.yml | 46 +-- ruoyi-common/pom.xml | 1 + ruoyi-common/ruoyi-common-bom/pom.xml | 7 + ruoyi-common/ruoyi-common-encrypt/pom.xml | 46 +++ .../common/encrypt/annotation/ApiEncrypt.java | 20 ++ .../encrypt/annotation/EncryptField.java | 44 +++ .../config/ApiDecryptAutoConfiguration.java | 32 ++ .../config/EncryptorAutoConfiguration.java | 41 +++ .../common/encrypt/core/EncryptContext.java | 41 +++ .../common/encrypt/core/EncryptorManager.java | 100 ++++++ .../ruoyi/common/encrypt/core/IEncryptor.java | 35 ++ .../core/encryptor/AbstractEncryptor.java | 18 + .../encrypt/core/encryptor/AesEncryptor.java | 55 ++++ .../core/encryptor/Base64Encryptor.java | 48 +++ .../encrypt/core/encryptor/RsaEncryptor.java | 62 ++++ .../encrypt/core/encryptor/Sm2Encryptor.java | 61 ++++ .../encrypt/core/encryptor/Sm4Encryptor.java | 55 ++++ .../common/encrypt/enumd/AlgorithmType.java | 49 +++ .../common/encrypt/enumd/EncodeType.java | 26 ++ .../common/encrypt/filter/CryptoFilter.java | 115 +++++++ .../filter/DecryptRequestBodyWrapper.java | 94 ++++++ .../filter/EncryptResponseBodyWrapper.java | 120 +++++++ .../MybatisDecryptInterceptor.java | 116 +++++++ .../MybatisEncryptInterceptor.java | 120 +++++++ .../properties/ApiDecryptProperties.java | 34 ++ .../properties/EncryptorProperties.java | 48 +++ .../common/encrypt/utils/EncryptUtils.java | 311 ++++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 3 + ruoyi-modules/ruoyi-system/pom.xml | 5 + 30 files changed, 1743 insertions(+), 19 deletions(-) create mode 100644 ruoyi-common/ruoyi-common-encrypt/pom.xml create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/annotation/ApiEncrypt.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/annotation/EncryptField.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/config/ApiDecryptAutoConfiguration.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/config/EncryptorAutoConfiguration.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/EncryptContext.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/EncryptorManager.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/IEncryptor.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/AbstractEncryptor.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/AesEncryptor.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/Base64Encryptor.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/RsaEncryptor.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/Sm2Encryptor.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/Sm4Encryptor.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/enumd/AlgorithmType.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/enumd/EncodeType.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/filter/CryptoFilter.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/filter/DecryptRequestBodyWrapper.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/filter/EncryptResponseBodyWrapper.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/interceptor/MybatisDecryptInterceptor.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/interceptor/MybatisEncryptInterceptor.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/properties/ApiDecryptProperties.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/properties/EncryptorProperties.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/utils/EncryptUtils.java create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/pom.xml b/pom.xml index a2dbeda..f7df0e9 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,8 @@ 2.7.0 1.12.600 + + 1.77 3.2.2 @@ -341,6 +343,13 @@ ${aws-java-sdk-s3.version} + + + org.bouncycastle + bcprov-jdk18on + ${bcprov-jdk.version} + + org.springframework.boot diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 248ddd1..9e260b9 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -150,6 +150,33 @@ mybatis-flex: # 默认的乐观锁字段 version-column: version +# 数据加密 +mybatis-encryptor: + # 是否开启加密 + enable: false + # 默认加密算法 + algorithm: BASE64 + # 编码方式 BASE64/HEX。默认BASE64 + encode: BASE64 + # 安全秘钥 对称算法的秘钥 如:AES,SM4 + password: + # 公私钥 非对称算法的公私钥 如:SM2,RSA + publicKey: + privateKey: + +# api接口加密 +api-decrypt: + # 是否开启全局接口加密 + enabled: true + # AES 加密头标识 + headerFlag: encrypt-key + # 响应加密公钥 非对称算法的公私钥 如:SM2,RSA 使用者请自行更换 + # 对应前端解密私钥 MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE= + publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJnNwrj4hi/y3CCJu868ghCG5dUj8wZK++RNlTLcXoMmdZWEQ/u02RgD5LyLAXGjLOjbMtC+/J9qofpSGTKSx/MCAwEAAQ== + # 请求解密私钥 非对称算法的公私钥 如:SM2,RSA 使用者请自行更换 + # 对应前端加密公钥 MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== + privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKNPuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gAkM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y= + # SpringDoc配置 springdoc: #需要扫描的包,可以配置多个,使用逗号分割 @@ -222,15 +249,6 @@ lock4j: # 分布式锁的超时时间,默认为 30 秒 expire: 30000 -## token配置 -#token: -# # 令牌自定义标识 -# header: Authorization -# # 令牌密钥 -# secret: abcdefghijklmnopqrstuvwxyz -# # 令牌有效期(默认30分钟) -# expireTime: 30 - # Sa-Token配置 sa-token: # token名称 (同时也是cookie名称) @@ -281,16 +299,6 @@ security: tenant: # 是否开启 enable: false - # 排除表 - excludes: - - sys_menu - - sys_tenant - - sys_tenant_package - - sys_role_dept - - sys_role_menu - - sys_user_post - - sys_user_role - - sys_client --- # Actuator 监控端点的配置项 management: diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index 983137a..f6d7b7c 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -13,6 +13,7 @@ ruoyi-common-bom ruoyi-common-core + ruoyi-common-encrypt ruoyi-common-excel ruoyi-common-job ruoyi-common-json diff --git a/ruoyi-common/ruoyi-common-bom/pom.xml b/ruoyi-common/ruoyi-common-bom/pom.xml index d6458af..b0ef088 100644 --- a/ruoyi-common/ruoyi-common-bom/pom.xml +++ b/ruoyi-common/ruoyi-common-bom/pom.xml @@ -26,6 +26,13 @@ ${revision} + + + com.ruoyi + ruoyi-common-encrypt + ${revision} + + com.ruoyi diff --git a/ruoyi-common/ruoyi-common-encrypt/pom.xml b/ruoyi-common/ruoyi-common-encrypt/pom.xml new file mode 100644 index 0000000..0fdbd12 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/pom.xml @@ -0,0 +1,46 @@ + + + + com.ruoyi + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-encrypt + + + ruoyi-common-encrypt 数据加解密模块 + + + + + + com.ruoyi + ruoyi-common-core + + + + org.bouncycastle + bcprov-jdk18on + + + + cn.hutool + hutool-crypto + + + com.ruoyi + ruoyi-common-orm + + + + org.springframework + spring-webmvc + + + + + diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/annotation/ApiEncrypt.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/annotation/ApiEncrypt.java new file mode 100644 index 0000000..d646a93 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/annotation/ApiEncrypt.java @@ -0,0 +1,20 @@ +package com.ruoyi.common.encrypt.annotation; + +import java.lang.annotation.*; + +/** + * 强制加密注解 + * + * @author Michelle.Chung + */ +@Documented +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiEncrypt { + + /** + * 响应加密忽略,默认不加密,为 true 时加密 + */ + boolean response() default false; + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/annotation/EncryptField.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/annotation/EncryptField.java new file mode 100644 index 0000000..0596bae --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/annotation/EncryptField.java @@ -0,0 +1,44 @@ +package com.ruoyi.common.encrypt.annotation; + +import com.ruoyi.common.encrypt.enumd.AlgorithmType; +import com.ruoyi.common.encrypt.enumd.EncodeType; + +import java.lang.annotation.*; + +/** + * 字段加密注解 + * + * @author 老马 + */ +@Documented +@Inherited +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface EncryptField { + + /** + * 加密算法 + */ + AlgorithmType algorithm() default AlgorithmType.DEFAULT; + + /** + * 秘钥。AES、SM4需要 + */ + String password() default ""; + + /** + * 公钥。RSA、SM2需要 + */ + String publicKey() default ""; + + /** + * 私钥。RSA、SM2需要 + */ + String privateKey() default ""; + + /** + * 编码方式。对加密算法为BASE64的不起作用 + */ + EncodeType encode() default EncodeType.DEFAULT; + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/config/ApiDecryptAutoConfiguration.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/config/ApiDecryptAutoConfiguration.java new file mode 100644 index 0000000..9b67271 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/config/ApiDecryptAutoConfiguration.java @@ -0,0 +1,32 @@ +package com.ruoyi.common.encrypt.config; + +import com.ruoyi.common.encrypt.filter.CryptoFilter; +import jakarta.servlet.DispatcherType; +import com.ruoyi.common.encrypt.properties.ApiDecryptProperties; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; + +/** + * api 解密自动配置 + * + * @author wdhcr + */ +@AutoConfiguration +@EnableConfigurationProperties(ApiDecryptProperties.class) +@ConditionalOnProperty(value = "api-decrypt.enabled", havingValue = "true") +public class ApiDecryptAutoConfiguration { + + @Bean + public FilterRegistrationBean cryptoFilterRegistration(ApiDecryptProperties properties) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setDispatcherTypes(DispatcherType.REQUEST); + registration.setFilter(new CryptoFilter(properties)); + registration.addUrlPatterns("/*"); + registration.setName("cryptoFilter"); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); + return registration; + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/config/EncryptorAutoConfiguration.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/config/EncryptorAutoConfiguration.java new file mode 100644 index 0000000..55be902 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/config/EncryptorAutoConfiguration.java @@ -0,0 +1,41 @@ +package com.ruoyi.common.encrypt.config; + +import com.ruoyi.common.encrypt.core.EncryptorManager; +import com.ruoyi.common.encrypt.interceptor.MybatisDecryptInterceptor; +import com.ruoyi.common.encrypt.interceptor.MybatisEncryptInterceptor; +import com.ruoyi.common.encrypt.properties.EncryptorProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +/** + * 加解密配置 + * + * @author 老马 + * @version 4.6.0 + */ +@AutoConfiguration +@EnableConfigurationProperties(EncryptorProperties.class) +@ConditionalOnProperty(value = "mybatis-encryptor.enable", havingValue = "true") +public class EncryptorAutoConfiguration { + + @Autowired + private EncryptorProperties properties; + + @Bean + public EncryptorManager encryptorManager() { + return new EncryptorManager(); + } + + @Bean + public MybatisEncryptInterceptor mybatisEncryptInterceptor(EncryptorManager encryptorManager) { + return new MybatisEncryptInterceptor(encryptorManager, properties); + } + + @Bean + public MybatisDecryptInterceptor mybatisDecryptInterceptor(EncryptorManager encryptorManager) { + return new MybatisDecryptInterceptor(encryptorManager, properties); + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/EncryptContext.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/EncryptContext.java new file mode 100644 index 0000000..30dcef1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/EncryptContext.java @@ -0,0 +1,41 @@ +package com.ruoyi.common.encrypt.core; + +import com.ruoyi.common.encrypt.enumd.EncodeType; +import lombok.Data; +import com.ruoyi.common.encrypt.enumd.AlgorithmType; + +/** + * 加密上下文 用于encryptor传递必要的参数。 + * + * @author 老马 + * @version 4.6.0 + */ +@Data +public class EncryptContext { + + /** + * 默认算法 + */ + private AlgorithmType algorithm; + + /** + * 安全秘钥 + */ + private String password; + + /** + * 公钥 + */ + private String publicKey; + + /** + * 私钥 + */ + private String privateKey; + + /** + * 编码方式,base64/hex + */ + private EncodeType encode; + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/EncryptorManager.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/EncryptorManager.java new file mode 100644 index 0000000..c8fa3ca --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/EncryptorManager.java @@ -0,0 +1,100 @@ +package com.ruoyi.common.encrypt.core; + +import cn.hutool.core.util.ReflectUtil; +import lombok.extern.slf4j.Slf4j; +import com.ruoyi.common.encrypt.annotation.EncryptField; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * 加密管理类 + * + * @author 老马 + * @version 4.6.0 + */ +@Slf4j +public class EncryptorManager { + + /** + * 缓存加密器 + */ + Map encryptorMap = new ConcurrentHashMap<>(); + + /** + * 类加密字段缓存 + */ + Map, Set> fieldCache = new ConcurrentHashMap<>(); + + /** + * 获取类加密字段缓存 + */ + public Set getFieldCache(Class sourceClazz) { + return fieldCache.computeIfAbsent(sourceClazz, clazz -> { + Set fieldSet = new HashSet<>(); + while (clazz != null) { + Field[] fields = clazz.getDeclaredFields(); + fieldSet.addAll(Arrays.asList(fields)); + clazz = clazz.getSuperclass(); + } + fieldSet = fieldSet.stream().filter(field -> + field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class) + .collect(Collectors.toSet()); + for (Field field : fieldSet) { + field.setAccessible(true); + } + return fieldSet; + }); + } + + /** + * 注册加密执行者到缓存 + * + * @param encryptContext 加密执行者需要的相关配置参数 + */ + public IEncryptor registAndGetEncryptor(EncryptContext encryptContext) { + if (encryptorMap.containsKey(encryptContext)) { + return encryptorMap.get(encryptContext); + } + IEncryptor encryptor = ReflectUtil.newInstance(encryptContext.getAlgorithm().getClazz(), encryptContext); + encryptorMap.put(encryptContext, encryptor); + return encryptor; + } + + /** + * 移除缓存中的加密执行者 + * + * @param encryptContext 加密执行者需要的相关配置参数 + */ + public void removeEncryptor(EncryptContext encryptContext) { + this.encryptorMap.remove(encryptContext); + } + + /** + * 根据配置进行加密。会进行本地缓存对应的算法和对应的秘钥信息。 + * + * @param value 待加密的值 + * @param encryptContext 加密相关的配置信息 + */ + public String encrypt(String value, EncryptContext encryptContext) { + IEncryptor encryptor = this.registAndGetEncryptor(encryptContext); + return encryptor.encrypt(value, encryptContext.getEncode()); + } + + /** + * 根据配置进行解密 + * + * @param value 待解密的值 + * @param encryptContext 加密相关的配置信息 + */ + public String decrypt(String value, EncryptContext encryptContext) { + IEncryptor encryptor = this.registAndGetEncryptor(encryptContext); + return encryptor.decrypt(value); + } + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/IEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/IEncryptor.java new file mode 100644 index 0000000..d9642c0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/IEncryptor.java @@ -0,0 +1,35 @@ +package com.ruoyi.common.encrypt.core; + +import com.ruoyi.common.encrypt.enumd.AlgorithmType; +import com.ruoyi.common.encrypt.enumd.EncodeType; + +/** + * 加解者 + * + * @author 老马 + * @version 4.6.0 + */ +public interface IEncryptor { + + /** + * 获得当前算法 + */ + AlgorithmType algorithm(); + + /** + * 加密 + * + * @param value 待加密字符串 + * @param encodeType 加密后的编码格式 + * @return 加密后的字符串 + */ + String encrypt(String value, EncodeType encodeType); + + /** + * 解密 + * + * @param value 待加密字符串 + * @return 解密后的字符串 + */ + String decrypt(String value); +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/AbstractEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/AbstractEncryptor.java new file mode 100644 index 0000000..35888f9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/AbstractEncryptor.java @@ -0,0 +1,18 @@ +package com.ruoyi.common.encrypt.core.encryptor; + +import com.ruoyi.common.encrypt.core.IEncryptor; +import com.ruoyi.common.encrypt.core.EncryptContext; + +/** + * 所有加密执行者的基类 + * + * @author 老马 + * @version 4.6.0 + */ +public abstract class AbstractEncryptor implements IEncryptor { + + public AbstractEncryptor(EncryptContext context) { + // 用户配置校验与配置注入 + } + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/AesEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/AesEncryptor.java new file mode 100644 index 0000000..2491efb --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/AesEncryptor.java @@ -0,0 +1,55 @@ +package com.ruoyi.common.encrypt.core.encryptor; + +import com.ruoyi.common.encrypt.core.EncryptContext; +import com.ruoyi.common.encrypt.enumd.AlgorithmType; +import com.ruoyi.common.encrypt.enumd.EncodeType; +import com.ruoyi.common.encrypt.utils.EncryptUtils; + +/** + * AES算法实现 + * + * @author 老马 + * @version 4.6.0 + */ +public class AesEncryptor extends AbstractEncryptor { + + private final EncryptContext context; + + public AesEncryptor(EncryptContext context) { + super(context); + this.context = context; + } + + /** + * 获得当前算法 + */ + @Override + public AlgorithmType algorithm() { + return AlgorithmType.AES; + } + + /** + * 加密 + * + * @param value 待加密字符串 + * @param encodeType 加密后的编码格式 + */ + @Override + public String encrypt(String value, EncodeType encodeType) { + if (encodeType == EncodeType.HEX) { + return EncryptUtils.encryptByAesHex(value, context.getPassword()); + } else { + return EncryptUtils.encryptByAes(value, context.getPassword()); + } + } + + /** + * 解密 + * + * @param value 待加密字符串 + */ + @Override + public String decrypt(String value) { + return EncryptUtils.decryptByAes(value, context.getPassword()); + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/Base64Encryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/Base64Encryptor.java new file mode 100644 index 0000000..abc3dcb --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/Base64Encryptor.java @@ -0,0 +1,48 @@ +package com.ruoyi.common.encrypt.core.encryptor; + +import com.ruoyi.common.encrypt.core.EncryptContext; +import com.ruoyi.common.encrypt.enumd.AlgorithmType; +import com.ruoyi.common.encrypt.enumd.EncodeType; +import com.ruoyi.common.encrypt.utils.EncryptUtils; + +/** + * Base64算法实现 + * + * @author 老马 + * @version 4.6.0 + */ +public class Base64Encryptor extends AbstractEncryptor { + + public Base64Encryptor(EncryptContext context) { + super(context); + } + + /** + * 获得当前算法 + */ + @Override + public AlgorithmType algorithm() { + return AlgorithmType.BASE64; + } + + /** + * 加密 + * + * @param value 待加密字符串 + * @param encodeType 加密后的编码格式 + */ + @Override + public String encrypt(String value, EncodeType encodeType) { + return EncryptUtils.encryptByBase64(value); + } + + /** + * 解密 + * + * @param value 待加密字符串 + */ + @Override + public String decrypt(String value) { + return EncryptUtils.decryptByBase64(value); + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/RsaEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/RsaEncryptor.java new file mode 100644 index 0000000..c9e9fa4 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/RsaEncryptor.java @@ -0,0 +1,62 @@ +package com.ruoyi.common.encrypt.core.encryptor; + +import com.ruoyi.common.core.utils.StringUtils; +import com.ruoyi.common.encrypt.core.EncryptContext; +import com.ruoyi.common.encrypt.enumd.AlgorithmType; +import com.ruoyi.common.encrypt.enumd.EncodeType; +import com.ruoyi.common.encrypt.utils.EncryptUtils; + + +/** + * RSA算法实现 + * + * @author 老马 + * @version 4.6.0 + */ +public class RsaEncryptor extends AbstractEncryptor { + + private final EncryptContext context; + + public RsaEncryptor(EncryptContext context) { + super(context); + String privateKey = context.getPrivateKey(); + String publicKey = context.getPublicKey(); + if (StringUtils.isAnyEmpty(privateKey, publicKey)) { + throw new IllegalArgumentException("RSA公私钥均需要提供,公钥加密,私钥解密。"); + } + this.context = context; + } + + /** + * 获得当前算法 + */ + @Override + public AlgorithmType algorithm() { + return AlgorithmType.RSA; + } + + /** + * 加密 + * + * @param value 待加密字符串 + * @param encodeType 加密后的编码格式 + */ + @Override + public String encrypt(String value, EncodeType encodeType) { + if (encodeType == EncodeType.HEX) { + return EncryptUtils.encryptByRsaHex(value, context.getPublicKey()); + } else { + return EncryptUtils.encryptByRsa(value, context.getPublicKey()); + } + } + + /** + * 解密 + * + * @param value 待加密字符串 + */ + @Override + public String decrypt(String value) { + return EncryptUtils.decryptByRsa(value, context.getPrivateKey()); + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/Sm2Encryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/Sm2Encryptor.java new file mode 100644 index 0000000..8c616d3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/Sm2Encryptor.java @@ -0,0 +1,61 @@ +package com.ruoyi.common.encrypt.core.encryptor; + +import com.ruoyi.common.core.utils.StringUtils; +import com.ruoyi.common.encrypt.core.EncryptContext; +import com.ruoyi.common.encrypt.enumd.AlgorithmType; +import com.ruoyi.common.encrypt.enumd.EncodeType; +import com.ruoyi.common.encrypt.utils.EncryptUtils; + +/** + * sm2算法实现 + * + * @author 老马 + * @version 4.6.0 + */ +public class Sm2Encryptor extends AbstractEncryptor { + + private final EncryptContext context; + + public Sm2Encryptor(EncryptContext context) { + super(context); + String privateKey = context.getPrivateKey(); + String publicKey = context.getPublicKey(); + if (StringUtils.isAnyEmpty(privateKey, publicKey)) { + throw new IllegalArgumentException("SM2公私钥均需要提供,公钥加密,私钥解密。"); + } + this.context = context; + } + + /** + * 获得当前算法 + */ + @Override + public AlgorithmType algorithm() { + return AlgorithmType.SM2; + } + + /** + * 加密 + * + * @param value 待加密字符串 + * @param encodeType 加密后的编码格式 + */ + @Override + public String encrypt(String value, EncodeType encodeType) { + if (encodeType == EncodeType.HEX) { + return EncryptUtils.encryptBySm2Hex(value, context.getPublicKey()); + } else { + return EncryptUtils.encryptBySm2(value, context.getPublicKey()); + } + } + + /** + * 解密 + * + * @param value 待加密字符串 + */ + @Override + public String decrypt(String value) { + return EncryptUtils.decryptBySm2(value, context.getPrivateKey()); + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/Sm4Encryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/Sm4Encryptor.java new file mode 100644 index 0000000..cdb8273 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/core/encryptor/Sm4Encryptor.java @@ -0,0 +1,55 @@ +package com.ruoyi.common.encrypt.core.encryptor; + +import com.ruoyi.common.encrypt.core.EncryptContext; +import com.ruoyi.common.encrypt.enumd.AlgorithmType; +import com.ruoyi.common.encrypt.enumd.EncodeType; +import com.ruoyi.common.encrypt.utils.EncryptUtils; + +/** + * sm4算法实现 + * + * @author 老马 + * @version 4.6.0 + */ +public class Sm4Encryptor extends AbstractEncryptor { + + private final EncryptContext context; + + public Sm4Encryptor(EncryptContext context) { + super(context); + this.context = context; + } + + /** + * 获得当前算法 + */ + @Override + public AlgorithmType algorithm() { + return AlgorithmType.SM4; + } + + /** + * 加密 + * + * @param value 待加密字符串 + * @param encodeType 加密后的编码格式 + */ + @Override + public String encrypt(String value, EncodeType encodeType) { + if (encodeType == EncodeType.HEX) { + return EncryptUtils.encryptBySm4Hex(value, context.getPassword()); + } else { + return EncryptUtils.encryptBySm4(value, context.getPassword()); + } + } + + /** + * 解密 + * + * @param value 待加密字符串 + */ + @Override + public String decrypt(String value) { + return EncryptUtils.decryptBySm4(value, context.getPassword()); + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/enumd/AlgorithmType.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/enumd/AlgorithmType.java new file mode 100644 index 0000000..8bb8de3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/enumd/AlgorithmType.java @@ -0,0 +1,49 @@ +package com.ruoyi.common.encrypt.enumd; + +import com.ruoyi.common.encrypt.core.encryptor.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import com.ruoyi.common.encrypt.core.encryptor.*; + +/** + * 算法名称 + * + * @author 老马 + * @version 4.6.0 + */ +@Getter +@AllArgsConstructor +public enum AlgorithmType { + + /** + * 默认走yml配置 + */ + DEFAULT(null), + + /** + * base64 + */ + BASE64(Base64Encryptor.class), + + /** + * aes + */ + AES(AesEncryptor.class), + + /** + * rsa + */ + RSA(RsaEncryptor.class), + + /** + * sm2 + */ + SM2(Sm2Encryptor.class), + + /** + * sm4 + */ + SM4(Sm4Encryptor.class); + + private final Class clazz; +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/enumd/EncodeType.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/enumd/EncodeType.java new file mode 100644 index 0000000..f8d5499 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/enumd/EncodeType.java @@ -0,0 +1,26 @@ +package com.ruoyi.common.encrypt.enumd; + +/** + * 编码类型 + * + * @author 老马 + * @version 4.6.0 + */ +public enum EncodeType { + + /** + * 默认使用yml配置 + */ + DEFAULT, + + /** + * base64编码 + */ + BASE64, + + /** + * 16进制编码 + */ + HEX; + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/filter/CryptoFilter.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/filter/CryptoFilter.java new file mode 100644 index 0000000..5b98b02 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/filter/CryptoFilter.java @@ -0,0 +1,115 @@ +package com.ruoyi.common.encrypt.filter; + +import cn.hutool.core.util.ObjectUtil; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import com.ruoyi.common.core.constant.HttpStatus; +import com.ruoyi.common.core.exception.ServiceException; +import com.ruoyi.common.core.utils.SpringUtils; +import com.ruoyi.common.core.utils.StringUtils; +import com.ruoyi.common.encrypt.annotation.ApiEncrypt; +import com.ruoyi.common.encrypt.properties.ApiDecryptProperties; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.HandlerExecutionChain; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.io.IOException; + + +/** + * Crypto 过滤器 + * + * @author wdhcr + */ +public class CryptoFilter implements Filter { + private final ApiDecryptProperties properties; + + public CryptoFilter(ApiDecryptProperties properties) { + this.properties = properties; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest servletRequest = (HttpServletRequest) request; + HttpServletResponse servletResponse = (HttpServletResponse) response; + + boolean encryptFlag = false; + ServletRequest requestWrapper = null; + ServletResponse responseWrapper = null; + EncryptResponseBodyWrapper responseBodyWrapper = null; + + // 是否为 json 请求 + if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { + // 是否为 put 或者 post 请求 + if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) { + // 是否存在加密标头 + String headerValue = servletRequest.getHeader(properties.getHeaderFlag()); + if (StringUtils.isNotBlank(headerValue)) { + // 请求解密 + requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPrivateKey(), properties.getHeaderFlag()); + // 获取加密注解 + ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest); + if (ObjectUtil.isNotNull(apiEncrypt)) { + // 响应加密标志 + encryptFlag = apiEncrypt.response(); + } else { + // 是否有注解,有就报错,没有放行 + HandlerExceptionResolver exceptionResolver = SpringUtils.getBean(HandlerExceptionResolver.class); + exceptionResolver.resolveException( + servletRequest, servletResponse, null, + new ServiceException("没有访问权限,请联系管理员授权", HttpStatus.FORBIDDEN)); + } + } + // 判断是否响应加密 + if (encryptFlag) { + responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse); + responseWrapper = responseBodyWrapper; + } + } + } + + chain.doFilter( + ObjectUtil.defaultIfNull(requestWrapper, request), + ObjectUtil.defaultIfNull(responseWrapper, response)); + + if (encryptFlag) { + servletResponse.reset(); + // 对原始内容加密 + String encryptContent = responseBodyWrapper.getEncryptContent( + servletResponse, properties.getPublicKey(), properties.getHeaderFlag()); + // 对加密后的内容写出 + servletResponse.getWriter().write(encryptContent); + } + } + + /** + * 获取 ApiEncrypt 注解 + */ + private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest servletRequest) { + RequestMappingHandlerMapping handlerMapping = SpringUtils.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class); + // 获取注解 + try { + HandlerExecutionChain mappingHandler = handlerMapping.getHandler(servletRequest); + if (ObjectUtil.isNotNull(mappingHandler)) { + Object handler = mappingHandler.getHandler(); + if (ObjectUtil.isNotNull(handler)) { + // 从handler获取注解 + if (handler instanceof HandlerMethod handlerMethod) { + return handlerMethod.getMethodAnnotation(ApiEncrypt.class); + } + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + return null; + } + + @Override + public void destroy() { + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/filter/DecryptRequestBodyWrapper.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/filter/DecryptRequestBodyWrapper.java new file mode 100644 index 0000000..83d1f3a --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/filter/DecryptRequestBodyWrapper.java @@ -0,0 +1,94 @@ +package com.ruoyi.common.encrypt.filter; + +import cn.hutool.core.io.IoUtil; +import com.ruoyi.common.encrypt.utils.EncryptUtils; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import com.ruoyi.common.core.constant.Constants; +import org.springframework.http.MediaType; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * 解密请求参数工具类 + * + * @author wdhcr + */ +public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper { + + private final byte[] body; + + public DecryptRequestBodyWrapper(HttpServletRequest request, String privateKey, String headerFlag) throws IOException { + super(request); + // 获取 AES 密码 采用 RSA 加密 + String headerRsa = request.getHeader(headerFlag); + String decryptAes = EncryptUtils.decryptByRsa(headerRsa, privateKey); + // 解密 AES 密码 + String aesPassword = EncryptUtils.decryptByBase64(decryptAes); + request.setCharacterEncoding(Constants.UTF8); + byte[] readBytes = IoUtil.readBytes(request.getInputStream(), false); + String requestBody = new String(readBytes, StandardCharsets.UTF_8); + // 解密 body 采用 AES 加密 + String decryptBody = EncryptUtils.decryptByAes(requestBody, aesPassword); + body = decryptBody.getBytes(StandardCharsets.UTF_8); + } + + @Override + public BufferedReader getReader() { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + + @Override + public int getContentLength() { + return body.length; + } + + @Override + public long getContentLengthLong() { + return body.length; + } + + @Override + public String getContentType() { + return MediaType.APPLICATION_JSON_VALUE; + } + + + @Override + public ServletInputStream getInputStream() { + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + return new ServletInputStream() { + @Override + public int read() { + return bais.read(); + } + + @Override + public int available() { + return body.length; + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + + } + }; + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/filter/EncryptResponseBodyWrapper.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/filter/EncryptResponseBodyWrapper.java new file mode 100644 index 0000000..243e646 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/filter/EncryptResponseBodyWrapper.java @@ -0,0 +1,120 @@ +package com.ruoyi.common.encrypt.filter; + +import cn.hutool.core.util.RandomUtil; +import com.ruoyi.common.encrypt.utils.EncryptUtils; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.WriteListener; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponseWrapper; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +/** + * 加密响应参数包装类 + * + * @author Michelle.Chung + */ +public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper { + + private final ByteArrayOutputStream byteArrayOutputStream; + private final ServletOutputStream servletOutputStream; + private final PrintWriter printWriter; + + public EncryptResponseBodyWrapper(HttpServletResponse response) throws IOException { + super(response); + this.byteArrayOutputStream = new ByteArrayOutputStream(); + this.servletOutputStream = this.getOutputStream(); + this.printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream)); + } + + @Override + public PrintWriter getWriter() { + return printWriter; + } + + @Override + public void flushBuffer() throws IOException { + if (servletOutputStream != null) { + servletOutputStream.flush(); + } + if (printWriter != null) { + printWriter.flush(); + } + } + + @Override + public void reset() { + byteArrayOutputStream.reset(); + } + + public byte[] getResponseData() throws IOException { + flushBuffer(); + return byteArrayOutputStream.toByteArray(); + } + + public String getContent() throws IOException { + flushBuffer(); + return byteArrayOutputStream.toString(); + } + + /** + * 获取加密内容 + * + * @param servletResponse response + * @param publicKey RSA公钥 (用于加密 AES 秘钥) + * @param headerFlag 请求头标志 + * @return 加密内容 + * @throws IOException + */ + public String getEncryptContent(HttpServletResponse servletResponse, String publicKey, String headerFlag) throws IOException { + // 生成秘钥 + String aesPassword = RandomUtil.randomString(32); + // 秘钥使用 Base64 编码 + String encryptAes = EncryptUtils.encryptByBase64(aesPassword); + // Rsa 公钥加密 Base64 编码 + String encryptPassword = EncryptUtils.encryptByRsa(encryptAes, publicKey); + + // 设置响应头 + servletResponse.setHeader(headerFlag, encryptPassword); + servletResponse.setHeader("Access-Control-Allow-Origin", "*"); + servletResponse.setHeader("Access-Control-Allow-Methods", "*"); + servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString()); + + // 获取原始内容 + String originalBody = this.getContent(); + // 对内容进行加密 + return EncryptUtils.encryptByAes(originalBody, aesPassword); + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + return new ServletOutputStream() { + @Override + public boolean isReady() { + return false; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + + } + + @Override + public void write(int b) throws IOException { + byteArrayOutputStream.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + byteArrayOutputStream.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + byteArrayOutputStream.write(b, off, len); + } + }; + } + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/interceptor/MybatisDecryptInterceptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/interceptor/MybatisDecryptInterceptor.java new file mode 100644 index 0000000..8fad74a --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/interceptor/MybatisDecryptInterceptor.java @@ -0,0 +1,116 @@ +package com.ruoyi.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.resultset.ResultSetHandler; +import org.apache.ibatis.plugin.*; +import com.ruoyi.common.core.utils.StringUtils; +import com.ruoyi.common.encrypt.annotation.EncryptField; +import com.ruoyi.common.encrypt.core.EncryptContext; +import com.ruoyi.common.encrypt.core.EncryptorManager; +import com.ruoyi.common.encrypt.enumd.AlgorithmType; +import com.ruoyi.common.encrypt.enumd.EncodeType; +import com.ruoyi.common.encrypt.properties.EncryptorProperties; + +import java.lang.reflect.Field; +import java.sql.Statement; +import java.util.*; + +/** + * 出参解密拦截器 + * + * @author 老马 + * @version 4.6.0 + */ +@Slf4j +@Intercepts({@Signature( + type = ResultSetHandler.class, + method = "handleResultSets", + args = {Statement.class}) +}) +@AllArgsConstructor +public class MybatisDecryptInterceptor implements Interceptor { + + private final EncryptorManager encryptorManager; + private final EncryptorProperties defaultProperties; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + // 获取执行mysql执行结果 + Object result = invocation.proceed(); + if (result == null) { + return null; + } + decryptHandler(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; + } + Set fields = encryptorManager.getFieldCache(sourceObject.getClass()); + 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); + } + + @Override + public void setProperties(Properties properties) { + + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/interceptor/MybatisEncryptInterceptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/interceptor/MybatisEncryptInterceptor.java new file mode 100644 index 0000000..bf529f9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/interceptor/MybatisEncryptInterceptor.java @@ -0,0 +1,120 @@ +package com.ruoyi.common.encrypt.interceptor; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import com.ruoyi.common.encrypt.annotation.EncryptField; +import com.ruoyi.common.encrypt.core.EncryptContext; +import com.ruoyi.common.encrypt.core.EncryptorManager; +import com.ruoyi.common.encrypt.enumd.AlgorithmType; +import com.ruoyi.common.encrypt.enumd.EncodeType; +import com.ruoyi.common.encrypt.properties.EncryptorProperties; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import com.ruoyi.common.core.utils.StringUtils; + +import java.lang.reflect.Field; +import java.sql.PreparedStatement; +import java.util.*; + +/** + * 入参加密拦截器 + * + * @author 老马 + * @version 4.6.0 + */ +@Slf4j +@Intercepts({@Signature( + type = ParameterHandler.class, + method = "setParameters", + args = {PreparedStatement.class}) +}) +@AllArgsConstructor +public class MybatisEncryptInterceptor implements Interceptor { + + private final EncryptorManager encryptorManager; + private final EncryptorProperties defaultProperties; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + return invocation; + } + + @Override + public Object plugin(Object target) { + if (target instanceof ParameterHandler parameterHandler) { + // 进行加密操作 + Object parameterObject = parameterHandler.getParameterObject(); + if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) { + this.encryptHandler(parameterObject); + } + } + return target; + } + + /** + * 加密对象 + * + * @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; + } + Set fields = encryptorManager.getFieldCache(sourceObject.getClass()); + 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/com/ruoyi/common/encrypt/properties/ApiDecryptProperties.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/properties/ApiDecryptProperties.java new file mode 100644 index 0000000..4070260 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/properties/ApiDecryptProperties.java @@ -0,0 +1,34 @@ +package com.ruoyi.common.encrypt.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * api解密属性配置类 + * @author wdhcr + */ +@Data +@ConfigurationProperties(prefix = "api-decrypt") +public class ApiDecryptProperties { + + /** + * 加密开关 + */ + private Boolean enabled; + + /** + * 头部标识 + */ + private String headerFlag; + + /** + * 响应加密公钥 + */ + private String publicKey; + + /** + * 请求解密私钥 + */ + private String privateKey; + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/properties/EncryptorProperties.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/properties/EncryptorProperties.java new file mode 100644 index 0000000..003db6c --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/properties/EncryptorProperties.java @@ -0,0 +1,48 @@ +package com.ruoyi.common.encrypt.properties; + +import com.ruoyi.common.encrypt.enumd.AlgorithmType; +import com.ruoyi.common.encrypt.enumd.EncodeType; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 加解密属性配置类 + * + * @author 老马 + * @version 4.6.0 + */ +@Data +@ConfigurationProperties(prefix = "mybatis-encryptor") +public class EncryptorProperties { + + /** + * 过滤开关 + */ + private Boolean enable; + + /** + * 默认算法 + */ + private AlgorithmType algorithm; + + /** + * 安全秘钥 + */ + private String password; + + /** + * 公钥 + */ + private String publicKey; + + /** + * 私钥 + */ + private String privateKey; + + /** + * 编码方式,base64/hex + */ + private EncodeType encode; + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/utils/EncryptUtils.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/utils/EncryptUtils.java new file mode 100644 index 0000000..e048faf --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/com/ruoyi/common/encrypt/utils/EncryptUtils.java @@ -0,0 +1,311 @@ +package com.ruoyi.common.encrypt.utils; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.SmUtil; +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.crypto.asymmetric.RSA; +import cn.hutool.crypto.asymmetric.SM2; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * 安全相关工具类 + * + * @author 老马 + */ +public class EncryptUtils { + /** + * 公钥 + */ + public static final String PUBLIC_KEY = "publicKey"; + /** + * 私钥 + */ + public static final String PRIVATE_KEY = "privateKey"; + + /** + * Base64加密 + * + * @param data 待加密数据 + * @return 加密后字符串 + */ + public static String encryptByBase64(String data) { + return Base64.encode(data, StandardCharsets.UTF_8); + } + + /** + * Base64解密 + * + * @param data 待解密数据 + * @return 解密后字符串 + */ + public static String decryptByBase64(String data) { + return Base64.decodeStr(data, StandardCharsets.UTF_8); + } + + /** + * AES加密 + * + * @param data 待解密数据 + * @param password 秘钥字符串 + * @return 加密后字符串, 采用Base64编码 + */ + public static String encryptByAes(String data, String password) { + if (StrUtil.isBlank(password)) { + throw new IllegalArgumentException("AES需要传入秘钥信息"); + } + // aes算法的秘钥要求是16位、24位、32位 + int[] array = {16, 24, 32}; + if (!ArrayUtil.contains(array, password.length())) { + throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位"); + } + return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8); + } + + /** + * AES加密 + * + * @param data 待解密数据 + * @param password 秘钥字符串 + * @return 加密后字符串, 采用Hex编码 + */ + public static String encryptByAesHex(String data, String password) { + if (StrUtil.isBlank(password)) { + throw new IllegalArgumentException("AES需要传入秘钥信息"); + } + // aes算法的秘钥要求是16位、24位、32位 + int[] array = {16, 24, 32}; + if (!ArrayUtil.contains(array, password.length())) { + throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位"); + } + return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptHex(data, StandardCharsets.UTF_8); + } + + /** + * AES解密 + * + * @param data 待解密数据 + * @param password 秘钥字符串 + * @return 解密后字符串 + */ + public static String decryptByAes(String data, String password) { + if (StrUtil.isBlank(password)) { + throw new IllegalArgumentException("AES需要传入秘钥信息"); + } + // aes算法的秘钥要求是16位、24位、32位 + int[] array = {16, 24, 32}; + if (!ArrayUtil.contains(array, password.length())) { + throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位"); + } + return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8); + } + + /** + * sm4加密 + * + * @param data 待加密数据 + * @param password 秘钥字符串 + * @return 加密后字符串, 采用Base64编码 + */ + public static String encryptBySm4(String data, String password) { + if (StrUtil.isBlank(password)) { + throw new IllegalArgumentException("SM4需要传入秘钥信息"); + } + // sm4算法的秘钥要求是16位长度 + int sm4PasswordLength = 16; + if (sm4PasswordLength != password.length()) { + throw new IllegalArgumentException("SM4秘钥长度要求为16位"); + } + return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8); + } + + /** + * sm4加密 + * + * @param data 待加密数据 + * @param password 秘钥字符串 + * @return 加密后字符串, 采用Base64编码 + */ + public static String encryptBySm4Hex(String data, String password) { + if (StrUtil.isBlank(password)) { + throw new IllegalArgumentException("SM4需要传入秘钥信息"); + } + // sm4算法的秘钥要求是16位长度 + int sm4PasswordLength = 16; + if (sm4PasswordLength != password.length()) { + throw new IllegalArgumentException("SM4秘钥长度要求为16位"); + } + return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptHex(data, StandardCharsets.UTF_8); + } + + /** + * sm4解密 + * + * @param data 待解密数据 + * @param password 秘钥字符串 + * @return 解密后字符串 + */ + public static String decryptBySm4(String data, String password) { + if (StrUtil.isBlank(password)) { + throw new IllegalArgumentException("SM4需要传入秘钥信息"); + } + // sm4算法的秘钥要求是16位长度 + int sm4PasswordLength = 16; + if (sm4PasswordLength != password.length()) { + throw new IllegalArgumentException("SM4秘钥长度要求为16位"); + } + return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8); + } + + /** + * 产生sm2加解密需要的公钥和私钥 + * + * @return 公私钥Map + */ + public static Map generateSm2Key() { + Map keyMap = new HashMap<>(2); + SM2 sm2 = SmUtil.sm2(); + keyMap.put(PRIVATE_KEY, sm2.getPrivateKeyBase64()); + keyMap.put(PUBLIC_KEY, sm2.getPublicKeyBase64()); + return keyMap; + } + + /** + * sm2公钥加密 + * + * @param data 待加密数据 + * @param publicKey 公钥 + * @return 加密后字符串, 采用Base64编码 + */ + public static String encryptBySm2(String data, String publicKey) { + if (StrUtil.isBlank(publicKey)) { + throw new IllegalArgumentException("SM2需要传入公钥进行加密"); + } + SM2 sm2 = SmUtil.sm2(null, publicKey); + return sm2.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey); + } + + /** + * sm2公钥加密 + * + * @param data 待加密数据 + * @param publicKey 公钥 + * @return 加密后字符串, 采用Hex编码 + */ + public static String encryptBySm2Hex(String data, String publicKey) { + if (StrUtil.isBlank(publicKey)) { + throw new IllegalArgumentException("SM2需要传入公钥进行加密"); + } + SM2 sm2 = SmUtil.sm2(null, publicKey); + return sm2.encryptHex(data, StandardCharsets.UTF_8, KeyType.PublicKey); + } + + /** + * sm2私钥解密 + * + * @param data 待加密数据 + * @param privateKey 私钥 + * @return 解密后字符串 + */ + public static String decryptBySm2(String data, String privateKey) { + if (StrUtil.isBlank(privateKey)) { + throw new IllegalArgumentException("SM2需要传入私钥进行解密"); + } + SM2 sm2 = SmUtil.sm2(privateKey, null); + return sm2.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8); + } + + /** + * 产生RSA加解密需要的公钥和私钥 + * + * @return 公私钥Map + */ + public static Map generateRsaKey() { + Map keyMap = new HashMap<>(2); + RSA rsa = SecureUtil.rsa(); + keyMap.put(PRIVATE_KEY, rsa.getPrivateKeyBase64()); + keyMap.put(PUBLIC_KEY, rsa.getPublicKeyBase64()); + return keyMap; + } + + /** + * rsa公钥加密 + * + * @param data 待加密数据 + * @param publicKey 公钥 + * @return 加密后字符串, 采用Base64编码 + */ + public static String encryptByRsa(String data, String publicKey) { + if (StrUtil.isBlank(publicKey)) { + throw new IllegalArgumentException("RSA需要传入公钥进行加密"); + } + RSA rsa = SecureUtil.rsa(null, publicKey); + return rsa.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey); + } + + /** + * rsa公钥加密 + * + * @param data 待加密数据 + * @param publicKey 公钥 + * @return 加密后字符串, 采用Hex编码 + */ + public static String encryptByRsaHex(String data, String publicKey) { + if (StrUtil.isBlank(publicKey)) { + throw new IllegalArgumentException("RSA需要传入公钥进行加密"); + } + RSA rsa = SecureUtil.rsa(null, publicKey); + return rsa.encryptHex(data, StandardCharsets.UTF_8, KeyType.PublicKey); + } + + /** + * rsa私钥解密 + * + * @param data 待加密数据 + * @param privateKey 私钥 + * @return 解密后字符串 + */ + public static String decryptByRsa(String data, String privateKey) { + if (StrUtil.isBlank(privateKey)) { + throw new IllegalArgumentException("RSA需要传入私钥进行解密"); + } + RSA rsa = SecureUtil.rsa(privateKey, null); + return rsa.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8); + } + + /** + * md5加密 + * + * @param data 待加密数据 + * @return 加密后字符串, 采用Hex编码 + */ + public static String encryptByMd5(String data) { + return SecureUtil.md5(data); + } + + /** + * sha256加密 + * + * @param data 待加密数据 + * @return 加密后字符串, 采用Hex编码 + */ + public static String encryptBySha256(String data) { + return SecureUtil.sha256(data); + } + + /** + * sm3加密 + * + * @param data 待加密数据 + * @return 加密后字符串, 采用Hex编码 + */ + public static String encryptBySm3(String data) { + return SmUtil.sm3(data); + } + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..fe61570 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,3 @@ +com.ruoyi.common.encrypt.config.EncryptorAutoConfiguration +com.ruoyi.common.encrypt.config.ApiDecryptAutoConfiguration + diff --git a/ruoyi-modules/ruoyi-system/pom.xml b/ruoyi-modules/ruoyi-system/pom.xml index 9b6ddc9..1fb3887 100644 --- a/ruoyi-modules/ruoyi-system/pom.xml +++ b/ruoyi-modules/ruoyi-system/pom.xml @@ -30,6 +30,11 @@ ruoyi-common-core + + com.ruoyi + ruoyi-common-encrypt + + com.ruoyi ruoyi-common-excel