mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-09-21 02:25:56 +08:00
Mybatis增强,使dao层实现对表字段的加解密功能
This commit is contained in:
parent
9ebaf936c1
commit
1625e25647
8
pom.xml
8
pom.xml
@ -36,6 +36,7 @@
|
||||
<alibaba-ttl.version>2.14.2</alibaba-ttl.version>
|
||||
<xxl-job.version>2.3.1</xxl-job.version>
|
||||
<lombok.version>1.18.24</lombok.version>
|
||||
<bouncycastle.version>1.72</bouncycastle.version>
|
||||
|
||||
<!-- 临时修复 snakeyaml 漏洞 -->
|
||||
<snakeyaml.version>1.33</snakeyaml.version>
|
||||
@ -252,6 +253,13 @@
|
||||
<version>${snakeyaml.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 加密包引入 -->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15to18</artifactId>
|
||||
<version>${bouncycastle.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 定时任务 -->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
|
||||
@ -183,6 +183,19 @@ mybatis-plus:
|
||||
updateStrategy: NOT_NULL
|
||||
# 字段验证策略之 select,在 select 的时候的字段验证策略既 wrapper 根据内部 entity 生成的 where 条件
|
||||
where-strategy: NOT_NULL
|
||||
# 数据加密
|
||||
mybatis-encryptor:
|
||||
# 是否开启加密
|
||||
enabled: true
|
||||
# 默认加密算法
|
||||
algorithm: aes
|
||||
# 安全秘钥。对称算法的秘钥。如:AES,SM4
|
||||
password: QBcc3SHK2G4ijyfl5XQ7@2g!N2j2jRDR
|
||||
# # 公私钥。非对称算法的公私钥,如:SM2,RSA
|
||||
# publicKey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCDt41GIcWvY6/tpjOLoWY78Oy00uWcNbMRG/8DRoS79/h2D+pV8uxV+0ezaN+fBFCZnK8TdJcPeU4EnNRUh/8HEY33KFvZ700n+Gj5BHUUDKzx3UVFNuF49UI/yoJ8rz6VQQHO79KK89VwmSMO77Tfee1ofe0STY6IwMt/MwaoKwIDAQAB
|
||||
# privateKey: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIO3jUYhxa9jr+2mM4uhZjvw7LTS5Zw1sxEb/wNGhLv3+HYP6lXy7FX7R7No358EUJmcrxN0lw95TgSc1FSH/wcRjfcoW9nvTSf4aPkEdRQMrPHdRUU24Xj1Qj/KgnyvPpVBAc7v0orz1XCZIw7vtN957Wh97RJNjojAy38zBqgrAgMBAAECgYAkXx4qvI6rFNryw88+Am6JpMioUghHb2ioE9QCYomqohnA+DocS71JLN8qwo3lijp7gJGzzKEeC8Aoc+oKAZfBQQwPP0K/ql+NcLdlqNAr+XyzkZrLJD/BfluQ6mXI23tjonWnPPZ1Et9HC3zXRQPiuh7Ff6UoqatTMI3OIbSyuQJBAPbaJpVYmIsWJdpvaAC6hcGgR/vbG9yX8VphPn8/2wsm2MmQrRkUoKkudE/veX3KQjGikDwo9+bT6aBl+nn2DUUCQQCImSGZFNcGqhMLyRGVogP6fh0YZSEQAhFbZ69nQmplqbie+dREhODH5Vs5F0C2aL5iSR/UaIDkNEA1auX7x56vAkAFWjON927PTTqi4tmBconl6eDFsDmJbe34xLUDM1I/iqcWr8FhEtZs9Knm9c1PkewfgWPZOhYt9hhRtwRYUqJ1AkAPvQ0I9US9KNVe80DKa8tnjiZODEDd9k8HqA+mpxlZM0/pSUGyz1iSz5NOJaa4HaNp8aDwOUY4hOis/u8Wrm5TAkEA00YeUsaXlMyMF/5pjols44tXb54AjAC2mH66pz9JsKg7pKpWVOpEV5rMY58CGZHWau69vGLZnCd1coeMw77YAQ==
|
||||
# # 编码方式,base64/hex。默认base64
|
||||
encode: base64
|
||||
|
||||
# Swagger配置
|
||||
swagger:
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
package com.ruoyi.test;
|
||||
|
||||
import com.ruoyi.demo.domain.TestDemo;
|
||||
import com.ruoyi.demo.mapper.TestDemoMapper;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 加密单元测试案例
|
||||
*
|
||||
* @author 老马
|
||||
* @date 2023-01-12 08:53
|
||||
*/
|
||||
@SpringBootTest
|
||||
@DisplayName("加密测试")
|
||||
public class EncryptUnitTest {
|
||||
|
||||
@Resource
|
||||
private TestDemoMapper demoMapper;
|
||||
|
||||
@Test
|
||||
public void testCrypt() {
|
||||
TestDemo demo = new TestDemo();
|
||||
demo.setTestKey("测试的key");
|
||||
demo.setValue("测试的value");
|
||||
this.demoMapper.insert(demo);
|
||||
System.out.println(demo);
|
||||
TestDemo testDemo = this.demoMapper.selectById(demo.getId());
|
||||
System.out.println(testDemo);
|
||||
}
|
||||
|
||||
}
|
||||
@ -148,6 +148,12 @@
|
||||
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 加密包引入 -->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15to18</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
package com.ruoyi.common.annotation;
|
||||
|
||||
import com.ruoyi.common.enums.AlgorithmType;
|
||||
import com.ruoyi.common.enums.EncodeType;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 字段加密注解
|
||||
*
|
||||
* @author 老马
|
||||
*/
|
||||
@Documented
|
||||
@Inherited
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface EncryptField {
|
||||
|
||||
/**
|
||||
* 加密算法
|
||||
*/
|
||||
AlgorithmType algorithm() default AlgorithmType.AES;
|
||||
|
||||
/**
|
||||
* 秘钥。AES、SM4需要
|
||||
*/
|
||||
String password() default "";
|
||||
|
||||
/**
|
||||
* 公钥。RSA、SM2需要
|
||||
*/
|
||||
String publicKey() default "";
|
||||
|
||||
/**
|
||||
* 公钥。RSA、SM2需要
|
||||
*/
|
||||
String privateKey() default "";
|
||||
|
||||
/**
|
||||
* 编码方式。对加密算法为BASE64的不起作用
|
||||
*/
|
||||
EncodeType encode() default EncodeType.BASE64;
|
||||
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package com.ruoyi.common.encrypt;
|
||||
|
||||
import com.ruoyi.common.enums.AlgorithmType;
|
||||
import com.ruoyi.common.enums.EncodeType;
|
||||
|
||||
/**
|
||||
* 加解者
|
||||
*
|
||||
* @author 老马
|
||||
* @date 2023-01-10 16:08
|
||||
*/
|
||||
public interface IEncryptor {
|
||||
|
||||
/**
|
||||
* 获得当前算法
|
||||
*
|
||||
* @return com.ruoyi.common.enums.AlgorithmType
|
||||
* @author 老马
|
||||
* @date 2023/1/11 11:18
|
||||
*/
|
||||
AlgorithmType algorithm();
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @param encodeType 加密后的编码格式
|
||||
* @return java.lang.String 加密后的字符串
|
||||
* @throws Exception 抛出异常
|
||||
* @author 老马
|
||||
* @date 2023/1/10 16:38
|
||||
*/
|
||||
String encrypt(String value, EncodeType encodeType) throws Exception;
|
||||
|
||||
/**
|
||||
* 解密
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @param encodeType 加密后的编码格式
|
||||
* @return java.lang.String 解密后的字符串
|
||||
* @throws Exception 抛出异常
|
||||
* @author 老马
|
||||
* @date 2023/1/10 16:38
|
||||
*/
|
||||
String decrypt(String value, EncodeType encodeType) throws Exception;
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package com.ruoyi.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 算法名称
|
||||
*
|
||||
* @author 老马
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum AlgorithmType {
|
||||
/**
|
||||
* base64
|
||||
*/
|
||||
BASE64("base64"),
|
||||
/**
|
||||
* aes
|
||||
*/
|
||||
AES("aes"),
|
||||
/**
|
||||
* rsa
|
||||
*/
|
||||
RSA("rsa"),
|
||||
/**
|
||||
* sm2
|
||||
*/
|
||||
SM2("sm2"),
|
||||
/**
|
||||
* sm4
|
||||
*/
|
||||
SM4("sm4");
|
||||
|
||||
private final String algorithm;
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package com.ruoyi.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 编码类型
|
||||
*
|
||||
* @author 老马
|
||||
* @date 2023-01-11 11:39
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum EncodeType {
|
||||
/**
|
||||
* base64编码
|
||||
*/
|
||||
BASE64("base64"),
|
||||
|
||||
/**
|
||||
* 16进制编码
|
||||
*/
|
||||
HEX("hex");
|
||||
|
||||
private final String encode;
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
package com.ruoyi.demo.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.ruoyi.common.annotation.EncryptField;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import com.ruoyi.common.enums.AlgorithmType;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@ -44,11 +46,13 @@ public class TestDemo extends BaseEntity {
|
||||
/**
|
||||
* key键
|
||||
*/
|
||||
@EncryptField(algorithm=AlgorithmType.SM2, privateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgZSlOvw8FBiH+aFJWLYZP/VRjg9wjfRarTkGBZd/T3N+gCgYIKoEcz1UBgi2hRANCAAR5DGuQwJqkxnbCsP+iPSDoHWIF4RwcR5EsSvT8QPxO1wRkR2IhCkzvRb32x2CUgJFdvoqVqfApFDPZzShqzBwX", publicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEeQxrkMCapMZ2wrD/oj0g6B1iBeEcHEeRLEr0/ED8TtcEZEdiIQpM70W99sdglICRXb6KlanwKRQz2c0oaswcFw==")
|
||||
private String testKey;
|
||||
|
||||
/**
|
||||
* 值
|
||||
*/
|
||||
@EncryptField(algorithm=AlgorithmType.AES, password = "10rfylhtccpuyke5")
|
||||
private String value;
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
package com.ruoyi.framework.config;
|
||||
|
||||
import com.ruoyi.framework.config.properties.EncryptorProperties;
|
||||
import com.ruoyi.framework.encrypt.EncryptorManager;
|
||||
import com.ruoyi.framework.encrypt.MybatisDecryptInterceptor;
|
||||
import com.ruoyi.framework.encrypt.MybatisEncryptInterceptor;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 加解密配置
|
||||
*
|
||||
* @author 老马
|
||||
* @date 2023-01-11 10:03
|
||||
*/
|
||||
@Configuration
|
||||
public class EncryptorConfig {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "mybatis-encryptor.enabled", havingValue = "true")
|
||||
public EncryptorManager mybatisCryptHandler(EncryptorProperties properties) {
|
||||
EncryptorManager encryptorManager = new EncryptorManager();
|
||||
encryptorManager.registAndGetEncryptor(properties);
|
||||
return encryptorManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "mybatis-encryptor.enabled", havingValue = "true")
|
||||
public MybatisEncryptInterceptor mybatisEncryptInterceptor(EncryptorProperties properties) {
|
||||
return new MybatisEncryptInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "mybatis-encryptor.enabled", havingValue = "true")
|
||||
public MybatisDecryptInterceptor mybatisDecryptInterceptor(EncryptorProperties properties) {
|
||||
return new MybatisDecryptInterceptor();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package com.ruoyi.framework.config.properties;
|
||||
|
||||
import com.ruoyi.common.enums.AlgorithmType;
|
||||
import com.ruoyi.common.enums.EncodeType;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 加解密属性配置类
|
||||
*
|
||||
* @author 老马
|
||||
* @date 2023-01-10 16:52
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "mybatis-encryptor")
|
||||
public class EncryptorProperties {
|
||||
|
||||
/**
|
||||
* 过滤开关
|
||||
*/
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* 默认算法
|
||||
*/
|
||||
private AlgorithmType algorithm;
|
||||
|
||||
/**
|
||||
* 安全秘钥
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 公钥
|
||||
*/
|
||||
private String publicKey;
|
||||
|
||||
/**
|
||||
* 私钥
|
||||
*/
|
||||
private String privateKey;
|
||||
|
||||
/**
|
||||
* 编码方式,base64/hex
|
||||
*/
|
||||
private EncodeType encode;
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package com.ruoyi.framework.encrypt;
|
||||
|
||||
import com.ruoyi.common.annotation.EncryptField;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 类加密字段缓存类
|
||||
*
|
||||
* @author 老马
|
||||
* @date 2023-01-12 11:07
|
||||
*/
|
||||
public class EncryptedFieldsCacheHelper {
|
||||
private static final Map<Class<?>, Set<Field>> ENCRYPTED_FIELD_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
public static Set<Field> get(Class<?> sourceClazz) {
|
||||
return ENCRYPTED_FIELD_CACHE.computeIfAbsent(sourceClazz, clazz -> {
|
||||
Field[] declaredFields = clazz.getDeclaredFields();
|
||||
Set<Field> fieldSet = Arrays.stream(declaredFields).filter(field ->
|
||||
field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class)
|
||||
.collect(Collectors.toSet());
|
||||
for (Field field : fieldSet) {
|
||||
field.setAccessible(true);
|
||||
}
|
||||
return fieldSet;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,125 @@
|
||||
package com.ruoyi.framework.encrypt;
|
||||
|
||||
import com.ruoyi.common.encrypt.IEncryptor;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.framework.config.properties.EncryptorProperties;
|
||||
import com.ruoyi.framework.encrypt.encryptor.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 加密管理类
|
||||
*
|
||||
* @author 老马
|
||||
* @date 2023-01-11 10:07
|
||||
*/
|
||||
@Slf4j
|
||||
public class EncryptorManager {
|
||||
|
||||
/**
|
||||
* 缓存加密器
|
||||
*/
|
||||
Map<String, IEncryptor> encryptorMap = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* 注册加密执行者到缓存
|
||||
*
|
||||
* @param properties 加密执行者需要的相关配置参数
|
||||
* @author 老马
|
||||
* @date 2023/1/11 15:09
|
||||
*/
|
||||
public IEncryptor registAndGetEncryptor(EncryptorProperties properties) {
|
||||
String encryptorKey = this.getEncryptorKeyFromProperties(properties);
|
||||
if(encryptorMap.containsKey(encryptorKey)){
|
||||
return encryptorMap.get(encryptorKey);
|
||||
}
|
||||
switch (properties.getAlgorithm()) {
|
||||
case BASE64:
|
||||
encryptorMap.put(encryptorKey, new Base64Encryptor());
|
||||
break;
|
||||
case AES:
|
||||
encryptorMap.put(encryptorKey, new AesEncryptor(properties.getPassword()));
|
||||
break;
|
||||
case RSA:
|
||||
encryptorMap.put(encryptorKey, new RsaEncryptor(properties.getPrivateKey(), properties.getPublicKey()));
|
||||
break;
|
||||
case SM2:
|
||||
encryptorMap.put(encryptorKey, new Sm2Encryptor(properties.getPrivateKey(), properties.getPublicKey()));
|
||||
break;
|
||||
case SM4:
|
||||
encryptorMap.put(encryptorKey, new Sm4Encryptor(properties.getPassword()));
|
||||
break;
|
||||
default:
|
||||
AesEncryptor defaultEncryptor = new AesEncryptor(properties.getPassword());
|
||||
encryptorMap.put(encryptorKey, defaultEncryptor);
|
||||
}
|
||||
return encryptorMap.get(encryptorKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除缓存中的加密执行者
|
||||
*
|
||||
* @param properties 加密执行者需要的相关配置参数
|
||||
* @author 老马
|
||||
* @date 2023/1/11 15:55
|
||||
*/
|
||||
public void removeEncryptor(EncryptorProperties properties) {
|
||||
this.encryptorMap.remove(getEncryptorKeyFromProperties(properties));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置进行加密。会进行本地缓存对应的算法和对应的秘钥信息。
|
||||
*
|
||||
* @param value 待加密的值
|
||||
* @param properties 加密相关的配置信息
|
||||
* @return java.lang.String 加密后的结果
|
||||
* @author 老马
|
||||
* @date 2023/1/11 16:46
|
||||
*/
|
||||
public String encrypt(String value, EncryptorProperties properties) {
|
||||
try {
|
||||
IEncryptor encryptor = this.registAndGetEncryptor(properties);
|
||||
return encryptor.encrypt(value, properties.getEncode());
|
||||
}catch (Exception e) {
|
||||
log.error("字段加密异常,原样返回。异常信息:【{}】", e.getMessage());
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置进行解密
|
||||
*
|
||||
* @param value 待解密的值
|
||||
* @param properties 加密相关的配置信息
|
||||
* @return java.lang.String
|
||||
* @author 老马
|
||||
* @date 2023/1/11 17:43
|
||||
*/
|
||||
public String decrypt(String value, EncryptorProperties properties) {
|
||||
try{
|
||||
IEncryptor encryptor = this.registAndGetEncryptor(properties);
|
||||
return encryptor.decrypt(value, properties.getEncode());
|
||||
}catch (Exception e) {
|
||||
log.error("字段解密异常,原样返回。异常信息:【{}】", e.getMessage());
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从配置内容中提取缓存的KEY
|
||||
*
|
||||
* @param properties 加密相关的配置信息
|
||||
* @return java.lang.String
|
||||
* @author 老马
|
||||
* @date 2023/1/11 17:39
|
||||
*/
|
||||
private String getEncryptorKeyFromProperties(EncryptorProperties properties) {
|
||||
return properties.getAlgorithm().getAlgorithm()+ StringUtils.defaultString(properties.getPassword()) +
|
||||
StringUtils.defaultString(properties.getPublicKey()) + StringUtils.defaultString(properties.getPrivateKey());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
package com.ruoyi.framework.encrypt;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import com.ruoyi.common.annotation.EncryptField;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
||||
import com.ruoyi.framework.config.properties.EncryptorProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.executor.resultset.ResultSetHandler;
|
||||
import org.apache.ibatis.plugin.*;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.sql.Statement;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 出参解密拦截器
|
||||
*
|
||||
* @author 老马
|
||||
* @date 2023-01-12 13:47
|
||||
*/
|
||||
@Slf4j
|
||||
@Intercepts({@Signature(
|
||||
type = ResultSetHandler.class,
|
||||
method = "handleResultSets",
|
||||
args = {Statement.class})
|
||||
})
|
||||
public class MybatisDecryptInterceptor implements Interceptor {
|
||||
|
||||
private final EncryptorManager encryptorManager = SpringUtils.getBean(EncryptorManager.class);
|
||||
private final EncryptorProperties defaultProperties = SpringUtils.getBean(EncryptorProperties.class);
|
||||
|
||||
@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 (sourceObject instanceof Map) {
|
||||
((Map<?, Object>) sourceObject).values().forEach(this::decryptHandler);
|
||||
return;
|
||||
}
|
||||
if (sourceObject instanceof List) {
|
||||
// 判断第一个元素是否含有注解。如果没有直接返回,提高效率
|
||||
Object firstItem = ((List<?>) sourceObject).get(0);
|
||||
if(CollectionUtil.isEmpty(EncryptedFieldsCacheHelper.get(firstItem.getClass()))) {
|
||||
return;
|
||||
}
|
||||
((List<?>) sourceObject).forEach(this::decryptHandler);
|
||||
return;
|
||||
}
|
||||
Set<Field> fields = EncryptedFieldsCacheHelper.get(sourceObject.getClass());
|
||||
try {
|
||||
for (Field field : fields) {
|
||||
field.set(sourceObject, this.decryptField(String.valueOf(field.get(sourceObject)), field));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理加密字段时出错:{}", ExceptionUtil.getSimpleMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段值进行加密。通过字段的批注注册新的加密算法
|
||||
*
|
||||
* @param value 待加密的值
|
||||
* @param field 待加密字段
|
||||
* @return 加密后结果
|
||||
*/
|
||||
private String decryptField(String value, Field field) {
|
||||
EncryptField encryptField = field.getAnnotation(EncryptField.class);
|
||||
EncryptorProperties properties = new EncryptorProperties();
|
||||
properties.setEnabled(true);
|
||||
properties.setAlgorithm(encryptField.algorithm());
|
||||
properties.setEncode(encryptField.encode());
|
||||
properties.setPassword(StringUtils.isEmpty(encryptField.password())?defaultProperties.getPassword():encryptField.password());
|
||||
properties.setPrivateKey(StringUtils.isEmpty(encryptField.privateKey())?defaultProperties.getPrivateKey():encryptField.privateKey());
|
||||
properties.setPublicKey(StringUtils.isEmpty(encryptField.publicKey())?defaultProperties.getPublicKey():encryptField.publicKey());
|
||||
this.encryptorManager.registAndGetEncryptor(properties);
|
||||
return this.encryptorManager.decrypt(value, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
return Plugin.wrap(target, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperties(Properties properties) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,112 @@
|
||||
package com.ruoyi.framework.encrypt;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.ruoyi.common.annotation.EncryptField;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
||||
import com.ruoyi.framework.config.properties.EncryptorProperties;
|
||||
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 java.lang.reflect.Field;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 入参加密拦截器
|
||||
*
|
||||
* @author 老马
|
||||
* @date 2023-01-10 16:42
|
||||
*/
|
||||
@Slf4j
|
||||
@Intercepts({@Signature(
|
||||
type = ParameterHandler.class,
|
||||
method = "setParameters",
|
||||
args = {PreparedStatement.class})
|
||||
})
|
||||
public class MybatisEncryptInterceptor implements Interceptor {
|
||||
|
||||
private final EncryptorManager encryptorManager = SpringUtils.getBean(EncryptorManager.class);
|
||||
private final EncryptorProperties defaultProperties = SpringUtils.getBean(EncryptorProperties.class);
|
||||
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
return invocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
if (target instanceof ParameterHandler) {
|
||||
// 进行加密操作
|
||||
ParameterHandler parameterHandler = (ParameterHandler) target;
|
||||
Object parameterObject = parameterHandler.getParameterObject();
|
||||
if(ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)){
|
||||
this.encryptHandler(parameterObject);
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密对象
|
||||
*
|
||||
* @param sourceObject 待加密对象
|
||||
*/
|
||||
private void encryptHandler (Object sourceObject) {
|
||||
if (sourceObject instanceof Map) {
|
||||
((Map<?, Object>) sourceObject).values().forEach(this::encryptHandler);
|
||||
return;
|
||||
}
|
||||
if (sourceObject instanceof List) {
|
||||
// 判断第一个元素是否含有注解。如果没有直接返回,提高效率
|
||||
Object firstItem = ((List<?>) sourceObject).get(0);
|
||||
if(CollectionUtil.isEmpty(EncryptedFieldsCacheHelper.get(firstItem.getClass()))) {
|
||||
return;
|
||||
}
|
||||
((List<?>) sourceObject).forEach(this::encryptHandler);
|
||||
return;
|
||||
}
|
||||
Set<Field> fields = EncryptedFieldsCacheHelper.get(sourceObject.getClass());
|
||||
try {
|
||||
for (Field field : fields) {
|
||||
field.set(sourceObject, this.encryptField(String.valueOf(field.get(sourceObject)), field));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理加密字段时出错:{}", ExceptionUtil.getSimpleMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段值进行加密。通过字段的批注注册新的加密算法
|
||||
*
|
||||
* @param value 待加密的值
|
||||
* @param field 待加密字段
|
||||
* @return 加密后结果
|
||||
*/
|
||||
private String encryptField(String value, Field field) {
|
||||
EncryptField encryptField = field.getAnnotation(EncryptField.class);
|
||||
EncryptorProperties properties = new EncryptorProperties();
|
||||
properties.setEnabled(true);
|
||||
properties.setAlgorithm(encryptField.algorithm());
|
||||
properties.setEncode(encryptField.encode());
|
||||
properties.setPassword(StringUtils.isEmpty(encryptField.password())?defaultProperties.getPassword():encryptField.password());
|
||||
properties.setPrivateKey(StringUtils.isEmpty(encryptField.privateKey())?defaultProperties.getPrivateKey():encryptField.privateKey());
|
||||
properties.setPublicKey(StringUtils.isEmpty(encryptField.publicKey())?defaultProperties.getPublicKey():encryptField.publicKey());
|
||||
this.encryptorManager.registAndGetEncryptor(properties);
|
||||
return this.encryptorManager.encrypt(value, properties);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setProperties(Properties properties) {
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
package com.ruoyi.framework.encrypt.encryptor;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.crypto.symmetric.AES;
|
||||
import com.ruoyi.common.encrypt.IEncryptor;
|
||||
import com.ruoyi.common.enums.AlgorithmType;
|
||||
import com.ruoyi.common.enums.EncodeType;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* AES算法实现
|
||||
*
|
||||
* @author 老马
|
||||
* @date 2023-01-06 11:39
|
||||
*/
|
||||
public class AesEncryptor implements IEncryptor {
|
||||
|
||||
private AES aes = null;
|
||||
|
||||
public AesEncryptor(String password) {
|
||||
aes = SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前算法
|
||||
*
|
||||
* @return com.ruoyi.common.enums.AlgorithmType
|
||||
* @author 老马
|
||||
* @date 2023/1/11 11:18
|
||||
*/
|
||||
@Override
|
||||
public AlgorithmType algorithm() {
|
||||
return AlgorithmType.AES;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @param encodeType 加密后的编码格式
|
||||
* @return java.lang.String
|
||||
* @author 老马
|
||||
* @date 2023/1/10 16:38
|
||||
*/
|
||||
@Override
|
||||
public String encrypt(String value, EncodeType encodeType) throws Exception {
|
||||
if(ObjectUtil.isNotNull(this.aes)) {
|
||||
if(encodeType == EncodeType.HEX) {
|
||||
return aes.encryptHex(value);
|
||||
}else {
|
||||
return aes.encryptBase64(value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @param encodeType 加密后的编码格式
|
||||
* @return java.lang.String
|
||||
* @author 老马
|
||||
* @date 2023/1/10 16:38
|
||||
*/
|
||||
@Override
|
||||
public String decrypt(String value,EncodeType encodeType) throws Exception {
|
||||
if(ObjectUtil.isNotNull(this.aes)) {
|
||||
return this.aes.decryptStr(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
package com.ruoyi.framework.encrypt.encryptor;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import com.ruoyi.common.encrypt.IEncryptor;
|
||||
import com.ruoyi.common.enums.AlgorithmType;
|
||||
import com.ruoyi.common.enums.EncodeType;
|
||||
|
||||
/**
|
||||
* Base64算法实现。不建议在生产环境使用
|
||||
*
|
||||
* @author 老马
|
||||
* @date 2023-01-06 10:00
|
||||
*/
|
||||
public class Base64Encryptor implements IEncryptor {
|
||||
/**
|
||||
* 获得当前算法
|
||||
*
|
||||
* @return com.ruoyi.common.enums.AlgorithmType
|
||||
* @author 老马
|
||||
* @date 2023/1/11 11:18
|
||||
*/
|
||||
@Override
|
||||
public AlgorithmType algorithm() {
|
||||
return AlgorithmType.BASE64;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @param encodeType 加密后的编码格式
|
||||
* @return java.lang.String
|
||||
* @author 老马
|
||||
* @date 2023/1/10 16:38
|
||||
*/
|
||||
@Override
|
||||
public String encrypt(String value, EncodeType encodeType) throws Exception {
|
||||
return Base64.encode(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @param encodeType 加密后的编码格式
|
||||
* @return java.lang.String
|
||||
* @author 老马
|
||||
* @date 2023/1/10 16:38
|
||||
*/
|
||||
@Override
|
||||
public String decrypt(String value, EncodeType encodeType) throws Exception {
|
||||
return Base64.decodeStr(value);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
package com.ruoyi.framework.encrypt.encryptor;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.crypto.asymmetric.KeyType;
|
||||
import cn.hutool.crypto.asymmetric.RSA;
|
||||
import com.ruoyi.common.encrypt.IEncryptor;
|
||||
import com.ruoyi.common.enums.AlgorithmType;
|
||||
import com.ruoyi.common.enums.EncodeType;
|
||||
|
||||
|
||||
/**
|
||||
* RSA算法实现
|
||||
*
|
||||
* @author 老马
|
||||
* @date 2023-01-06 09:37
|
||||
*/
|
||||
public class RsaEncryptor implements IEncryptor {
|
||||
|
||||
private RSA rsa = null;
|
||||
|
||||
public RsaEncryptor(String privateKey, String publicKey) {
|
||||
this.rsa = SecureUtil.rsa(Base64.decode(privateKey), Base64.decode(publicKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前算法
|
||||
*
|
||||
* @return com.ruoyi.common.enums.AlgorithmType
|
||||
* @author 老马
|
||||
* @date 2023/1/11 11:18
|
||||
*/
|
||||
@Override
|
||||
public AlgorithmType algorithm() {
|
||||
return AlgorithmType.RSA;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @param encodeType 加密后的编码格式
|
||||
* @return java.lang.String
|
||||
* @author 老马
|
||||
* @date 2023/1/10 16:38
|
||||
*/
|
||||
@Override
|
||||
public String encrypt(String value, EncodeType encodeType) throws Exception {
|
||||
if(ObjectUtil.isNotNull(this.rsa)) {
|
||||
if (encodeType == EncodeType.HEX) {
|
||||
return rsa.encryptHex(value, KeyType.PublicKey);
|
||||
} else {
|
||||
return rsa.encryptBase64(value, KeyType.PublicKey);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @param encodeType 加密后的编码格式
|
||||
* @return java.lang.String
|
||||
* @author 老马
|
||||
* @date 2023/1/10 16:38
|
||||
*/
|
||||
@Override
|
||||
public String decrypt(String value, EncodeType encodeType) throws Exception {
|
||||
if(ObjectUtil.isNotNull(this.rsa)) {
|
||||
return this.rsa.decryptStr(value, KeyType.PrivateKey);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
package com.ruoyi.framework.encrypt.encryptor;
|
||||
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.crypto.SmUtil;
|
||||
import cn.hutool.crypto.asymmetric.KeyType;
|
||||
import cn.hutool.crypto.asymmetric.SM2;
|
||||
import com.ruoyi.common.encrypt.IEncryptor;
|
||||
import com.ruoyi.common.enums.AlgorithmType;
|
||||
import com.ruoyi.common.enums.EncodeType;
|
||||
|
||||
/**
|
||||
* sm2算法实现
|
||||
*
|
||||
* @author 老马
|
||||
* @date 2023-01-06 17:13
|
||||
*/
|
||||
public class Sm2Encryptor implements IEncryptor {
|
||||
|
||||
private SM2 sm2 = null;
|
||||
|
||||
public Sm2Encryptor(String privateKey, String publicKey) {
|
||||
sm2 = SmUtil.sm2(Base64.decode(privateKey), Base64.decode(publicKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前算法
|
||||
*
|
||||
* @return com.ruoyi.common.enums.AlgorithmType
|
||||
* @author 老马
|
||||
* @date 2023/1/11 11:18
|
||||
*/
|
||||
@Override
|
||||
public AlgorithmType algorithm() {
|
||||
return AlgorithmType.SM2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @param encodeType 加密后的编码格式
|
||||
* @return java.lang.String
|
||||
* @throws Exception 抛出异常
|
||||
* @author 老马
|
||||
* @date 2023/1/10 16:38
|
||||
*/
|
||||
@Override
|
||||
public String encrypt(String value, EncodeType encodeType) throws Exception {
|
||||
if(ObjectUtil.isNotNull(this.sm2)) {
|
||||
if (encodeType == EncodeType.HEX) {
|
||||
return sm2.encryptHex(value, KeyType.PublicKey);
|
||||
} else {
|
||||
return sm2.encryptBase64(value, KeyType.PublicKey);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @param encodeType 加密后的编码格式
|
||||
* @return java.lang.String
|
||||
* @throws Exception 抛出异常
|
||||
* @author 老马
|
||||
* @date 2023/1/10 16:38
|
||||
*/
|
||||
@Override
|
||||
public String decrypt(String value, EncodeType encodeType) throws Exception {
|
||||
if(ObjectUtil.isNotNull(this.sm2)) {
|
||||
return this.sm2.decryptStr(value, KeyType.PrivateKey);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package com.ruoyi.framework.encrypt.encryptor;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.crypto.SmUtil;
|
||||
import cn.hutool.crypto.symmetric.SM4;
|
||||
import com.ruoyi.common.encrypt.IEncryptor;
|
||||
import com.ruoyi.common.enums.AlgorithmType;
|
||||
import com.ruoyi.common.enums.EncodeType;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* sm4算法实现
|
||||
*
|
||||
* @author 老马
|
||||
* @date 2023-01-06 17:40
|
||||
*/
|
||||
public class Sm4Encryptor implements IEncryptor {
|
||||
|
||||
private SM4 sm4 = null;
|
||||
|
||||
public Sm4Encryptor(String password) {
|
||||
this.sm4 = SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前算法
|
||||
*
|
||||
* @return com.ruoyi.common.enums.AlgorithmType
|
||||
* @author 老马
|
||||
* @date 2023/1/11 11:18
|
||||
*/
|
||||
@Override
|
||||
public AlgorithmType algorithm() {
|
||||
return AlgorithmType.SM4;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @param encodeType 加密后的编码格式
|
||||
* @return java.lang.String
|
||||
* @throws Exception 抛出异常
|
||||
* @author 老马
|
||||
* @date 2023/1/10 16:38
|
||||
*/
|
||||
@Override
|
||||
public String encrypt(String value, EncodeType encodeType) throws Exception {
|
||||
if(ObjectUtil.isNotNull(this.sm4)) {
|
||||
if (encodeType == EncodeType.HEX) {
|
||||
return sm4.encryptHex(value);
|
||||
} else {
|
||||
return sm4.encryptBase64(value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @param encodeType 加密后的编码格式
|
||||
* @return java.lang.String
|
||||
* @throws Exception 抛出异常
|
||||
* @author 老马
|
||||
* @date 2023/1/10 16:38
|
||||
*/
|
||||
@Override
|
||||
public String decrypt(String value, EncodeType encodeType) throws Exception {
|
||||
if(ObjectUtil.isNotNull(this.sm4)) {
|
||||
return this.sm4.decryptStr(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user