init: 导入RuoYi‑Vue‑Plus 6.X完整代码
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ruoyi-common-encrypt</artifactId>
|
||||
|
||||
<description>
|
||||
ruoyi-common-encrypt 数据加解密模块
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 加密包引入 -->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk18on</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Hutool 加密工具 -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring WebMVC -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Plus 启动器 -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package org.dromara.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;
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package org.dromara.common.encrypt.annotation;
|
||||
|
||||
import org.dromara.common.encrypt.enums.AlgorithmType;
|
||||
import org.dromara.common.encrypt.enums.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;
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package org.dromara.common.encrypt.config;
|
||||
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import org.dromara.common.encrypt.filter.CryptoFilter;
|
||||
import org.dromara.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.FilterRegistration;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
/**
|
||||
* api 解密自动配置
|
||||
*
|
||||
* @author wdhcr
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(ApiDecryptProperties.class)
|
||||
@ConditionalOnProperty(value = "api-decrypt.enabled", havingValue = "true")
|
||||
public class ApiDecryptAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 注册 API 加解密过滤器。
|
||||
*
|
||||
* @param properties API 解密配置
|
||||
* @param requestMappingHandlerMapping 请求映射处理器
|
||||
* @param handlerExceptionResolver 异常处理器
|
||||
* @return API 加解密过滤器
|
||||
*/
|
||||
@Bean
|
||||
@FilterRegistration(
|
||||
name = "cryptoFilter",
|
||||
urlPatterns = "/*",
|
||||
order = FilterRegistrationBean.HIGHEST_PRECEDENCE,
|
||||
dispatcherTypes = DispatcherType.REQUEST
|
||||
)
|
||||
public CryptoFilter cryptoFilter(ApiDecryptProperties properties,
|
||||
RequestMappingHandlerMapping requestMappingHandlerMapping,
|
||||
HandlerExceptionResolver handlerExceptionResolver) {
|
||||
return new CryptoFilter(properties, requestMappingHandlerMapping, handlerExceptionResolver);
|
||||
}
|
||||
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package org.dromara.common.encrypt.config;
|
||||
|
||||
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
|
||||
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.encrypt.core.EncryptContextFactory;
|
||||
import org.dromara.common.encrypt.core.EncryptedFieldProcessor;
|
||||
import org.dromara.common.encrypt.core.EncryptorManager;
|
||||
import org.dromara.common.encrypt.enums.AlgorithmType;
|
||||
import org.dromara.common.encrypt.interceptor.MybatisDecryptInterceptor;
|
||||
import org.dromara.common.encrypt.interceptor.MybatisEncryptInterceptor;
|
||||
import org.dromara.common.encrypt.properties.EncryptorProperties;
|
||||
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 Lion Li
|
||||
*/
|
||||
@AutoConfiguration(after = MybatisPlusAutoConfiguration.class)
|
||||
@EnableConfigurationProperties(EncryptorProperties.class)
|
||||
@ConditionalOnProperty(value = "mybatis-encryptor.enable", havingValue = "true")
|
||||
@Slf4j
|
||||
public class EncryptorAutoConfiguration {
|
||||
|
||||
@Autowired
|
||||
private EncryptorProperties properties;
|
||||
|
||||
/**
|
||||
* 创建字段加解密管理器。
|
||||
*
|
||||
* @param mybatisPlusProperties MyBatis-Plus 配置
|
||||
* @return 字段加解密管理器
|
||||
*/
|
||||
@Bean
|
||||
public EncryptorManager encryptorManager(MybatisPlusProperties mybatisPlusProperties) {
|
||||
validateEncryptorProperties(properties);
|
||||
return new EncryptorManager(mybatisPlusProperties.getTypeAliasesPackage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建加密上下文工厂。
|
||||
*
|
||||
* @return 加密上下文工厂
|
||||
*/
|
||||
@Bean
|
||||
public EncryptContextFactory encryptContextFactory() {
|
||||
return new EncryptContextFactory(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建加密字段处理器。
|
||||
*
|
||||
* @param encryptorManager 加解密管理器
|
||||
* @param encryptContextFactory 加密上下文工厂
|
||||
* @return 加密字段处理器
|
||||
*/
|
||||
@Bean
|
||||
public EncryptedFieldProcessor encryptedFieldProcessor(EncryptorManager encryptorManager, EncryptContextFactory encryptContextFactory) {
|
||||
return new EncryptedFieldProcessor(encryptorManager, encryptContextFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 MyBatis 入参加密拦截器。
|
||||
*
|
||||
* @param encryptedFieldProcessor 加密字段处理器
|
||||
* @return MyBatis 入参加密拦截器
|
||||
*/
|
||||
@Bean
|
||||
public MybatisEncryptInterceptor mybatisEncryptInterceptor(EncryptedFieldProcessor encryptedFieldProcessor) {
|
||||
return new MybatisEncryptInterceptor(encryptedFieldProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 MyBatis 出参解密拦截器。
|
||||
*
|
||||
* @param encryptedFieldProcessor 加密字段处理器
|
||||
* @return MyBatis 出参解密拦截器
|
||||
*/
|
||||
@Bean
|
||||
public MybatisDecryptInterceptor mybatisDecryptInterceptor(EncryptedFieldProcessor encryptedFieldProcessor) {
|
||||
return new MybatisDecryptInterceptor(encryptedFieldProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验字段加解密配置完整性。
|
||||
*
|
||||
* @param properties 字段加解密配置
|
||||
*/
|
||||
private void validateEncryptorProperties(EncryptorProperties properties) {
|
||||
AlgorithmType algorithm = properties.getAlgorithm();
|
||||
if (algorithm == AlgorithmType.AES || algorithm == AlgorithmType.SM4) {
|
||||
if (StringUtils.isBlank(properties.getPassword())) {
|
||||
throw new IllegalArgumentException("mybatis-encryptor.password 不能为空");
|
||||
}
|
||||
}
|
||||
if (algorithm == AlgorithmType.RSA || algorithm == AlgorithmType.SM2) {
|
||||
if (StringUtils.isAnyBlank(properties.getPublicKey(), properties.getPrivateKey())) {
|
||||
throw new IllegalArgumentException("mybatis-encryptor.publicKey 与 privateKey 不能为空");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package org.dromara.common.encrypt.core;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.common.encrypt.enums.AlgorithmType;
|
||||
import org.dromara.common.encrypt.enums.EncodeType;
|
||||
|
||||
/**
|
||||
* 加密上下文 用于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;
|
||||
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package org.dromara.common.encrypt.core;
|
||||
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.encrypt.annotation.EncryptField;
|
||||
import org.dromara.common.encrypt.enums.AlgorithmType;
|
||||
import org.dromara.common.encrypt.enums.EncodeType;
|
||||
import org.dromara.common.encrypt.properties.EncryptorProperties;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* 加密上下文工厂。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public class EncryptContextFactory {
|
||||
|
||||
private final EncryptorProperties defaultProperties;
|
||||
|
||||
/**
|
||||
* 构造加密上下文工厂。
|
||||
*
|
||||
* @param defaultProperties 默认加密配置
|
||||
*/
|
||||
public EncryptContextFactory(EncryptorProperties defaultProperties) {
|
||||
this.defaultProperties = defaultProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字段注解和默认配置创建加密上下文。
|
||||
*
|
||||
* @param field 加密字段
|
||||
* @return 加密上下文
|
||||
*/
|
||||
public EncryptContext create(Field field) {
|
||||
EncryptField encryptField = field.getAnnotation(EncryptField.class);
|
||||
EncryptContext encryptContext = new EncryptContext();
|
||||
encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());
|
||||
encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode());
|
||||
encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password());
|
||||
encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey());
|
||||
encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey());
|
||||
return encryptContext;
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
package org.dromara.common.encrypt.core;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 加密字段处理器。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
public class EncryptedFieldProcessor {
|
||||
|
||||
private final EncryptorManager encryptorManager;
|
||||
private final EncryptContextFactory contextFactory;
|
||||
|
||||
/**
|
||||
* 构造加密字段处理器。
|
||||
*
|
||||
* @param encryptorManager 加解密管理器
|
||||
* @param contextFactory 加密上下文工厂
|
||||
*/
|
||||
public EncryptedFieldProcessor(EncryptorManager encryptorManager, EncryptContextFactory contextFactory) {
|
||||
this.encryptorManager = encryptorManager;
|
||||
this.contextFactory = contextFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密对象字段,并返回原始字段快照。
|
||||
*
|
||||
* @param sourceObject 待加密对象
|
||||
* @return 原始字段快照
|
||||
*/
|
||||
public List<FieldSnapshot> encrypt(Object sourceObject) {
|
||||
List<FieldSnapshot> snapshots = new ArrayList<>();
|
||||
handle(sourceObject, Collections.newSetFromMap(new IdentityHashMap<>()), (target, field, value) -> {
|
||||
String encrypt = encryptorManager.encrypt(value, contextFactory.create(field));
|
||||
if (!Objects.equals(value, encrypt)) {
|
||||
snapshots.add(new FieldSnapshot(target, field, value));
|
||||
field.set(target, encrypt);
|
||||
}
|
||||
});
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密对象字段。
|
||||
*
|
||||
* @param sourceObject 待解密对象
|
||||
*/
|
||||
public void decrypt(Object sourceObject) {
|
||||
handle(sourceObject, Collections.newSetFromMap(new IdentityHashMap<>()), (target, field, value) ->
|
||||
field.set(target, encryptorManager.decrypt(value, contextFactory.create(field))));
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归处理对象、集合或 Map 中声明加密注解的字段。
|
||||
*
|
||||
* @param sourceObject 待处理对象
|
||||
* @param visited 已访问对象集合
|
||||
* @param fieldHandler 字段处理回调
|
||||
*/
|
||||
private void handle(Object sourceObject, Set<Object> visited, FieldHandler fieldHandler) {
|
||||
if (ObjectUtil.isNull(sourceObject) || sourceObject instanceof String || visited.contains(sourceObject)) {
|
||||
return;
|
||||
}
|
||||
visited.add(sourceObject);
|
||||
if (sourceObject instanceof Map<?, ?> map) {
|
||||
new HashSet<>(map.values()).forEach(value -> handle(value, visited, fieldHandler));
|
||||
return;
|
||||
}
|
||||
if (sourceObject instanceof Collection<?> collection) {
|
||||
if (CollUtil.isEmpty(collection)) {
|
||||
return;
|
||||
}
|
||||
collection.forEach(item -> handle(item, visited, fieldHandler));
|
||||
return;
|
||||
}
|
||||
Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
|
||||
if (CollUtil.isEmpty(fields)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
for (Field field : fields) {
|
||||
String value = Convert.toStr(field.get(sourceObject));
|
||||
if (ObjectUtil.isNotNull(value)) {
|
||||
fieldHandler.handle(sourceObject, field, value);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理加密字段时出错", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密字段处理回调。
|
||||
*/
|
||||
@FunctionalInterface
|
||||
private interface FieldHandler {
|
||||
|
||||
/**
|
||||
* 处理单个加密字段。
|
||||
*
|
||||
* @param target 字段所属对象
|
||||
* @param field 加密字段
|
||||
* @param value 字段原始字符串值
|
||||
* @throws IllegalAccessException 字段访问失败时抛出
|
||||
*/
|
||||
void handle(Object target, Field field, String value) throws IllegalAccessException;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段原始值快照。
|
||||
*/
|
||||
public record FieldSnapshot(Object target, Field field, Object value) {
|
||||
|
||||
/**
|
||||
* 恢复原始字段值。
|
||||
*/
|
||||
public void restore() {
|
||||
try {
|
||||
field.set(target, value);
|
||||
} catch (IllegalAccessException e) {
|
||||
log.error("恢复加密字段时出错", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
package org.dromara.common.encrypt.core;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.io.Resources;
|
||||
import org.dromara.common.core.constant.Constants;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.encrypt.annotation.EncryptField;
|
||||
import org.dromara.common.encrypt.enums.AlgorithmType;
|
||||
import org.dromara.common.encrypt.enums.EncodeType;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
import org.springframework.core.type.ClassMetadata;
|
||||
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
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 Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
public class EncryptorManager {
|
||||
|
||||
/**
|
||||
* 缓存加密器
|
||||
*/
|
||||
Map<EncryptorCacheKey, IEncryptor> encryptorMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 类加密字段缓存
|
||||
*/
|
||||
Map<Class<?>, Set<Field>> fieldCache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 构造方法传入类加密字段缓存
|
||||
*
|
||||
* @param typeAliasesPackage 实体类包
|
||||
*/
|
||||
public EncryptorManager(String typeAliasesPackage) {
|
||||
scanEncryptClasses(typeAliasesPackage);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取类加密字段缓存
|
||||
*/
|
||||
public Set<Field> getFieldCache(Class<?> sourceClazz) {
|
||||
if (sourceClazz == null || sourceClazz.isPrimitive() || sourceClazz.isArray()
|
||||
|| sourceClazz.isEnum() || sourceClazz.isAnnotation()
|
||||
|| ClassUtils.isPrimitiveOrWrapper(sourceClazz)
|
||||
|| sourceClazz.getName().startsWith("java.")) {
|
||||
return Set.of();
|
||||
}
|
||||
return fieldCache.computeIfAbsent(sourceClazz, this::getEncryptFieldSetFromClazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册加密执行者到缓存
|
||||
*
|
||||
* @param encryptContext 加密执行者需要的相关配置参数
|
||||
*/
|
||||
public IEncryptor registAndGetEncryptor(EncryptContext encryptContext) {
|
||||
EncryptorCacheKey key = EncryptorCacheKey.of(encryptContext);
|
||||
return encryptorMap.computeIfAbsent(key, cacheKey ->
|
||||
ReflectUtil.newInstance(cacheKey.algorithm().getClazz(), encryptContext));
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除缓存中的加密执行者
|
||||
*
|
||||
* @param encryptContext 加密执行者需要的相关配置参数
|
||||
*/
|
||||
public void removeEncryptor(EncryptContext encryptContext) {
|
||||
this.encryptorMap.remove(EncryptorCacheKey.of(encryptContext));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置进行加密。会进行本地缓存对应的算法和对应的秘钥信息。
|
||||
*
|
||||
* @param value 待加密的值
|
||||
* @param encryptContext 加密相关的配置信息
|
||||
*/
|
||||
public String encrypt(String value, EncryptContext encryptContext) {
|
||||
if (StringUtils.startsWith(value, Constants.ENCRYPT_HEADER)) {
|
||||
return value;
|
||||
}
|
||||
IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
|
||||
String encrypt = encryptor.encrypt(value, encryptContext.getEncode());
|
||||
return Constants.ENCRYPT_HEADER + encrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置进行解密
|
||||
*
|
||||
* @param value 待解密的值
|
||||
* @param encryptContext 加密相关的配置信息
|
||||
*/
|
||||
public String decrypt(String value, EncryptContext encryptContext) {
|
||||
if (!StringUtils.startsWith(value, Constants.ENCRYPT_HEADER)) {
|
||||
return value;
|
||||
}
|
||||
IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
|
||||
String str = StringUtils.removeStart(value, Constants.ENCRYPT_HEADER);
|
||||
return encryptor.decrypt(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 typeAliasesPackage 设置的扫描包 扫描缓存实体
|
||||
*/
|
||||
private void scanEncryptClasses(String typeAliasesPackage) {
|
||||
if (StringUtils.isBlank(typeAliasesPackage)) {
|
||||
return;
|
||||
}
|
||||
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
|
||||
String[] packagePatternArray = StringUtils.splitPreserveAllTokens(typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
|
||||
String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
|
||||
try {
|
||||
for (String packagePattern : packagePatternArray) {
|
||||
String path = ClassUtils.convertClassNameToResourcePath(packagePattern);
|
||||
Resource[] resources = resolver.getResources(classpath + path + "/*.class");
|
||||
for (Resource resource : resources) {
|
||||
ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();
|
||||
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
|
||||
Set<Field> encryptFieldSet = getEncryptFieldSetFromClazz(clazz);
|
||||
if (CollUtil.isNotEmpty(encryptFieldSet)) {
|
||||
fieldCache.put(clazz, encryptFieldSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("初始化数据安全缓存时出错:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得一个类的加密字段集合
|
||||
*/
|
||||
private Set<Field> getEncryptFieldSetFromClazz(Class<?> clazz) {
|
||||
Set<Field> fieldSet = new HashSet<>();
|
||||
// 判断clazz如果是接口,内部类,匿名类就直接返回
|
||||
if (clazz.isInterface() || clazz.isMemberClass() || clazz.isAnonymousClass()) {
|
||||
return Set.of();
|
||||
}
|
||||
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 algorithm 加密算法
|
||||
* @param encode 编码方式
|
||||
* @param password 密码
|
||||
* @param publicKey 公钥
|
||||
* @param privateKey 私钥
|
||||
*/
|
||||
private record EncryptorCacheKey(
|
||||
AlgorithmType algorithm,
|
||||
EncodeType encode,
|
||||
String password,
|
||||
String publicKey,
|
||||
String privateKey
|
||||
) {
|
||||
|
||||
/**
|
||||
* 从加密上下文构建缓存键。
|
||||
*
|
||||
* @param encryptContext 加密上下文
|
||||
* @return 加密器缓存键
|
||||
*/
|
||||
private static EncryptorCacheKey of(EncryptContext encryptContext) {
|
||||
return new EncryptorCacheKey(
|
||||
encryptContext.getAlgorithm(),
|
||||
encryptContext.getEncode(),
|
||||
encryptContext.getPassword(),
|
||||
encryptContext.getPublicKey(),
|
||||
encryptContext.getPrivateKey());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package org.dromara.common.encrypt.core;
|
||||
|
||||
import org.dromara.common.encrypt.enums.AlgorithmType;
|
||||
import org.dromara.common.encrypt.enums.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);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package org.dromara.common.encrypt.core.encryptor;
|
||||
|
||||
import org.dromara.common.encrypt.core.EncryptContext;
|
||||
import org.dromara.common.encrypt.core.IEncryptor;
|
||||
|
||||
/**
|
||||
* 所有加密执行者的基类
|
||||
*
|
||||
* @author 老马
|
||||
* @version 4.6.0
|
||||
*/
|
||||
public abstract class AbstractEncryptor implements IEncryptor {
|
||||
|
||||
/**
|
||||
* 初始化加密执行者。
|
||||
*
|
||||
* @param context 加密上下文
|
||||
*/
|
||||
public AbstractEncryptor(EncryptContext context) {
|
||||
// 用户配置校验与配置注入
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package org.dromara.common.encrypt.core.encryptor;
|
||||
|
||||
import org.dromara.common.encrypt.core.EncryptContext;
|
||||
import org.dromara.common.encrypt.enums.AlgorithmType;
|
||||
import org.dromara.common.encrypt.enums.EncodeType;
|
||||
import org.dromara.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());
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package org.dromara.common.encrypt.core.encryptor;
|
||||
|
||||
import org.dromara.common.encrypt.core.EncryptContext;
|
||||
import org.dromara.common.encrypt.enums.AlgorithmType;
|
||||
import org.dromara.common.encrypt.enums.EncodeType;
|
||||
import org.dromara.common.encrypt.utils.EncryptUtils;
|
||||
|
||||
/**
|
||||
* Base64算法实现
|
||||
*
|
||||
* @author 老马
|
||||
* @version 4.6.0
|
||||
*/
|
||||
public class Base64Encryptor extends AbstractEncryptor {
|
||||
|
||||
/**
|
||||
* 初始化 Base64 加密执行者。
|
||||
*
|
||||
* @param context 加密上下文
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package org.dromara.common.encrypt.core.encryptor;
|
||||
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.encrypt.core.EncryptContext;
|
||||
import org.dromara.common.encrypt.enums.AlgorithmType;
|
||||
import org.dromara.common.encrypt.enums.EncodeType;
|
||||
import org.dromara.common.encrypt.utils.EncryptUtils;
|
||||
|
||||
|
||||
/**
|
||||
* RSA算法实现
|
||||
*
|
||||
* @author 老马
|
||||
* @version 4.6.0
|
||||
*/
|
||||
public class RsaEncryptor extends AbstractEncryptor {
|
||||
|
||||
private final EncryptContext context;
|
||||
|
||||
/**
|
||||
* 构造 RSA 加密器。
|
||||
*
|
||||
* @param 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());
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package org.dromara.common.encrypt.core.encryptor;
|
||||
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.encrypt.core.EncryptContext;
|
||||
import org.dromara.common.encrypt.enums.AlgorithmType;
|
||||
import org.dromara.common.encrypt.enums.EncodeType;
|
||||
import org.dromara.common.encrypt.utils.EncryptUtils;
|
||||
|
||||
/**
|
||||
* sm2算法实现
|
||||
*
|
||||
* @author 老马
|
||||
* @version 4.6.0
|
||||
*/
|
||||
public class Sm2Encryptor extends AbstractEncryptor {
|
||||
|
||||
private final EncryptContext context;
|
||||
|
||||
/**
|
||||
* 构造 SM2 加密器。
|
||||
*
|
||||
* @param 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());
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package org.dromara.common.encrypt.core.encryptor;
|
||||
|
||||
import org.dromara.common.encrypt.core.EncryptContext;
|
||||
import org.dromara.common.encrypt.enums.AlgorithmType;
|
||||
import org.dromara.common.encrypt.enums.EncodeType;
|
||||
import org.dromara.common.encrypt.utils.EncryptUtils;
|
||||
|
||||
/**
|
||||
* sm4算法实现
|
||||
*
|
||||
* @author 老马
|
||||
* @version 4.6.0
|
||||
*/
|
||||
public class Sm4Encryptor extends AbstractEncryptor {
|
||||
|
||||
private final EncryptContext context;
|
||||
|
||||
/**
|
||||
* 构造 SM4 加密器。
|
||||
*
|
||||
* @param 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());
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package org.dromara.common.encrypt.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.dromara.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<? extends AbstractEncryptor> clazz;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package org.dromara.common.encrypt.enums;
|
||||
|
||||
/**
|
||||
* 编码类型
|
||||
*
|
||||
* @author 老马
|
||||
* @version 4.6.0
|
||||
*/
|
||||
public enum EncodeType {
|
||||
|
||||
/**
|
||||
* 默认使用yml配置
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
/**
|
||||
* base64编码
|
||||
*/
|
||||
BASE64,
|
||||
|
||||
/**
|
||||
* 16进制编码
|
||||
*/
|
||||
HEX
|
||||
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
package org.dromara.common.encrypt.filter;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.dromara.common.core.constant.HttpStatus;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.encrypt.annotation.ApiEncrypt;
|
||||
import org.dromara.common.encrypt.properties.ApiDecryptProperties;
|
||||
import org.dromara.common.encrypt.utils.EncryptUtils;
|
||||
import org.springframework.http.HttpMethod;
|
||||
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;
|
||||
private final RequestMappingHandlerMapping requestMappingHandlerMapping;
|
||||
private final HandlerExceptionResolver handlerExceptionResolver;
|
||||
|
||||
/**
|
||||
* 构造加解密过滤器。
|
||||
*
|
||||
* @param properties API 解密配置
|
||||
* @param requestMappingHandlerMapping 请求映射处理器
|
||||
* @param handlerExceptionResolver 异常处理器
|
||||
*/
|
||||
public CryptoFilter(ApiDecryptProperties properties,
|
||||
RequestMappingHandlerMapping requestMappingHandlerMapping,
|
||||
HandlerExceptionResolver handlerExceptionResolver) {
|
||||
this.properties = properties;
|
||||
this.requestMappingHandlerMapping = requestMappingHandlerMapping;
|
||||
this.handlerExceptionResolver = handlerExceptionResolver;
|
||||
EncryptUtils.validateRsaPublicKey(properties.getPublicKey());
|
||||
EncryptUtils.validateRsaPrivateKey(properties.getPrivateKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据接口注解与请求头执行请求解密和响应加密。
|
||||
*
|
||||
* @param request 原始请求
|
||||
* @param response 原始响应
|
||||
* @param chain 过滤器链
|
||||
* @throws IOException IO 异常
|
||||
* @throws ServletException Servlet 异常
|
||||
*/
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest servletRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse servletResponse = (HttpServletResponse) response;
|
||||
// 获取加密注解
|
||||
ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest);
|
||||
boolean responseFlag = apiEncrypt != null && apiEncrypt.response();
|
||||
ServletRequest requestWrapper = null;
|
||||
ServletResponse responseWrapper = null;
|
||||
EncryptResponseBodyWrapper responseBodyWrapper = null;
|
||||
|
||||
// 是否为 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());
|
||||
} else {
|
||||
// 是否有注解,有就报错,没有放行
|
||||
if (ObjectUtil.isNotNull(apiEncrypt)) {
|
||||
handlerExceptionResolver.resolveException(
|
||||
servletRequest, servletResponse, null,
|
||||
new ServiceException("没有访问权限,请联系管理员授权", HttpStatus.FORBIDDEN));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否响应加密
|
||||
if (responseFlag) {
|
||||
responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse);
|
||||
responseWrapper = responseBodyWrapper;
|
||||
}
|
||||
|
||||
chain.doFilter(
|
||||
ObjectUtil.defaultIfNull(requestWrapper, request),
|
||||
ObjectUtil.defaultIfNull(responseWrapper, response));
|
||||
|
||||
if (responseFlag) {
|
||||
// 对原始内容加密
|
||||
String encryptContent = responseBodyWrapper.getEncryptContent(
|
||||
servletResponse, properties.getPublicKey(), properties.getHeaderFlag());
|
||||
// 对加密后的内容写出
|
||||
servletResponse.getWriter().write(encryptContent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ApiEncrypt 注解
|
||||
*
|
||||
* @param servletRequest 当前请求
|
||||
* @return API 加密注解
|
||||
*/
|
||||
private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest servletRequest) {
|
||||
// 获取注解
|
||||
try {
|
||||
HandlerExecutionChain mappingHandler = requestMappingHandlerMapping.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) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁过滤器。
|
||||
*/
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package org.dromara.common.encrypt.filter;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import jakarta.servlet.ReadListener;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import org.dromara.common.core.constant.Constants;
|
||||
import org.dromara.common.encrypt.utils.EncryptUtils;
|
||||
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.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 解密请求参数工具类
|
||||
*
|
||||
* @author wdhcr
|
||||
*/
|
||||
public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
/**
|
||||
* 请求体字节数据。
|
||||
*/
|
||||
private final byte[] body;
|
||||
|
||||
/**
|
||||
* 解密请求体并缓存为可重复读取的 JSON 请求体。
|
||||
*
|
||||
* @param request 原始请求
|
||||
* @param privateKey RSA 私钥
|
||||
* @param headerFlag 加密密钥请求头
|
||||
* @throws IOException 读取请求体异常
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于解密后的请求体创建字符读取器。
|
||||
*
|
||||
* @return 字符读取器
|
||||
*/
|
||||
@Override
|
||||
public BufferedReader getReader() {
|
||||
Charset charset = Charset.forName(getCharacterEncoding());
|
||||
return new BufferedReader(new InputStreamReader(getInputStream(), charset));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回解密后请求体长度。
|
||||
*
|
||||
* @return 请求体长度
|
||||
*/
|
||||
@Override
|
||||
public int getContentLength() {
|
||||
return body.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回解密后请求体长度。
|
||||
*
|
||||
* @return 请求体长度
|
||||
*/
|
||||
@Override
|
||||
public long getContentLengthLong() {
|
||||
return body.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回解密后的请求体类型。
|
||||
*
|
||||
* @return JSON 内容类型
|
||||
*/
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return MediaType.APPLICATION_JSON_VALUE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回基于解密请求体的输入流。
|
||||
*
|
||||
* @return 解密请求体输入流
|
||||
*/
|
||||
@Override
|
||||
public ServletInputStream getInputStream() {
|
||||
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
|
||||
return new ServletInputStream() {
|
||||
/**
|
||||
* 读取解密请求体下一个字节。
|
||||
*
|
||||
* @return 下一个字节
|
||||
*/
|
||||
@Override
|
||||
public int read() {
|
||||
return bais.read();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回解密请求体剩余可读字节数。
|
||||
*
|
||||
* @return 剩余字节数
|
||||
*/
|
||||
@Override
|
||||
public int available() {
|
||||
return bais.available();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断解密请求体是否读取完毕。
|
||||
*
|
||||
* @return 是否读取完毕
|
||||
*/
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return bais.available() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断解密请求体输入流是否可读。
|
||||
*
|
||||
* @return 固定为 true
|
||||
*/
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置异步读取监听器。
|
||||
*
|
||||
* @param readListener 读取监听器
|
||||
*/
|
||||
@Override
|
||||
public void setReadListener(ReadListener readListener) {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
package org.dromara.common.encrypt.filter;
|
||||
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.WriteListener;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletResponseWrapper;
|
||||
import org.dromara.common.encrypt.utils.EncryptUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* 加密响应参数包装类
|
||||
*
|
||||
* @author Michelle.Chung
|
||||
*/
|
||||
public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper {
|
||||
|
||||
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||
private static final Charset RESPONSE_CHARSET = StandardCharsets.UTF_8;
|
||||
|
||||
private final ByteArrayOutputStream byteArrayOutputStream;
|
||||
private final ServletOutputStream servletOutputStream;
|
||||
private PrintWriter printWriter;
|
||||
|
||||
/**
|
||||
* 构造加密响应包装器。
|
||||
*
|
||||
* @param response 原始响应
|
||||
* @throws IOException 创建输出流异常
|
||||
*/
|
||||
public EncryptResponseBodyWrapper(HttpServletResponse response) throws IOException {
|
||||
super(response);
|
||||
this.byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
this.servletOutputStream = this.getOutputStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回缓存响应内容的字符输出器。
|
||||
*
|
||||
* @return 字符输出器
|
||||
*/
|
||||
@Override
|
||||
public PrintWriter getWriter() {
|
||||
if (printWriter == null) {
|
||||
printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream, RESPONSE_CHARSET));
|
||||
}
|
||||
return printWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新缓存的响应输出流和字符输出器。
|
||||
*
|
||||
* @throws IOException IO 异常
|
||||
*/
|
||||
@Override
|
||||
public void flushBuffer() throws IOException {
|
||||
if (servletOutputStream != null) {
|
||||
servletOutputStream.flush();
|
||||
}
|
||||
if (printWriter != null) {
|
||||
printWriter.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置已缓存的响应内容。
|
||||
*/
|
||||
@Override
|
||||
public void reset() {
|
||||
byteArrayOutputStream.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置已缓存的响应缓冲区。
|
||||
*/
|
||||
@Override
|
||||
public void resetBuffer() {
|
||||
byteArrayOutputStream.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已缓存的响应字节。
|
||||
*
|
||||
* @return 响应字节数组
|
||||
* @throws IOException 刷新响应缓冲异常
|
||||
*/
|
||||
public byte[] getResponseData() throws IOException {
|
||||
flushBuffer();
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已缓存的响应内容。
|
||||
*
|
||||
* @return 响应文本
|
||||
* @throws IOException 刷新响应缓冲异常
|
||||
*/
|
||||
public String getContent() throws IOException {
|
||||
flushBuffer();
|
||||
return byteArrayOutputStream.toString(RESPONSE_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取加密内容
|
||||
*
|
||||
* @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 = generateAesPassword();
|
||||
// 秘钥使用 Base64 编码
|
||||
String encryptAes = EncryptUtils.encryptByBase64(aesPassword);
|
||||
// Rsa 公钥加密 Base64 编码
|
||||
String encryptPassword = EncryptUtils.encryptByRsa(encryptAes, publicKey);
|
||||
|
||||
// 设置响应头
|
||||
// vue版本需要设置
|
||||
servletResponse.addHeader("Access-Control-Expose-Headers", headerFlag);
|
||||
servletResponse.setHeader(headerFlag, encryptPassword);
|
||||
servletResponse.setCharacterEncoding(RESPONSE_CHARSET.name());
|
||||
|
||||
|
||||
// 获取原始内容
|
||||
String originalBody = this.getContent();
|
||||
// 对内容进行加密
|
||||
String encryptContent = EncryptUtils.encryptByAes(originalBody, aesPassword);
|
||||
servletResponse.setContentLengthLong(encryptContent.getBytes(RESPONSE_CHARSET).length);
|
||||
return encryptContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回缓存响应内容的二进制输出流。
|
||||
*
|
||||
* @return 响应输出流
|
||||
*/
|
||||
@Override
|
||||
public ServletOutputStream getOutputStream() throws IOException {
|
||||
return new ServletOutputStream() {
|
||||
/**
|
||||
* 判断响应输出流是否可写。
|
||||
*
|
||||
* @return 固定为 true
|
||||
*/
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置异步写入监听器。
|
||||
*
|
||||
* @param writeListener 写入监听器
|
||||
*/
|
||||
@Override
|
||||
public void setWriteListener(WriteListener writeListener) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入单个字节到响应缓存。
|
||||
*
|
||||
* @param b 待写入字节
|
||||
* @throws IOException IO 异常
|
||||
*/
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
byteArrayOutputStream.write(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入字节数组到响应缓存。
|
||||
*
|
||||
* @param b 待写入字节数组
|
||||
* @throws IOException IO 异常
|
||||
*/
|
||||
@Override
|
||||
public void write(byte[] b) throws IOException {
|
||||
byteArrayOutputStream.write(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入字节数组片段到响应缓存。
|
||||
*
|
||||
* @param b 待写入字节数组
|
||||
* @param off 起始偏移量
|
||||
* @param len 写入长度
|
||||
* @throws IOException IO 异常
|
||||
*/
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
byteArrayOutputStream.write(b, off, len);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成响应内容 AES 加密密钥。
|
||||
*
|
||||
* @return AES 密钥
|
||||
*/
|
||||
private String generateAesPassword() {
|
||||
byte[] bytes = new byte[24];
|
||||
SECURE_RANDOM.nextBytes(bytes);
|
||||
return Base64.getEncoder().encodeToString(bytes);
|
||||
}
|
||||
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package org.dromara.common.encrypt.interceptor;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.ibatis.executor.resultset.ResultSetHandler;
|
||||
import org.apache.ibatis.plugin.*;
|
||||
import org.dromara.common.encrypt.core.EncryptedFieldProcessor;
|
||||
|
||||
import java.sql.Statement;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 出参解密拦截器
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Intercepts({@Signature(
|
||||
type = ResultSetHandler.class,
|
||||
method = "handleResultSets",
|
||||
args = {Statement.class})
|
||||
})
|
||||
@AllArgsConstructor
|
||||
public class MybatisDecryptInterceptor implements Interceptor {
|
||||
|
||||
private final EncryptedFieldProcessor encryptedFieldProcessor;
|
||||
|
||||
/**
|
||||
* 解密 MyBatis 查询结果中的加密字段。
|
||||
*
|
||||
* @param invocation 拦截调用信息
|
||||
* @return 查询结果
|
||||
* @throws Throwable 拦截处理异常
|
||||
*/
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
// 获取执行mysql执行结果
|
||||
Object result = invocation.proceed();
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
encryptedFieldProcessor.decrypt(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装 MyBatis 目标对象。
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @return 包装后的对象
|
||||
*/
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
return Plugin.wrap(target, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置插件属性。
|
||||
*
|
||||
* @param properties 插件属性
|
||||
*/
|
||||
@Override
|
||||
public void setProperties(Properties properties) {
|
||||
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package org.dromara.common.encrypt.interceptor;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.ibatis.executor.parameter.ParameterHandler;
|
||||
import org.apache.ibatis.plugin.*;
|
||||
import org.dromara.common.encrypt.core.EncryptedFieldProcessor;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 入参加密拦截器
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Intercepts({@Signature(
|
||||
type = ParameterHandler.class,
|
||||
method = "setParameters",
|
||||
args = {PreparedStatement.class})
|
||||
})
|
||||
@AllArgsConstructor
|
||||
public class MybatisEncryptInterceptor implements Interceptor {
|
||||
|
||||
private final EncryptedFieldProcessor encryptedFieldProcessor;
|
||||
|
||||
/**
|
||||
* 加密 MyBatis 入参中的加密字段,并在执行后恢复原始值。
|
||||
*
|
||||
* @param invocation 拦截调用信息
|
||||
* @return MyBatis 执行结果
|
||||
* @throws Throwable 拦截处理异常
|
||||
*/
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
List<EncryptedFieldProcessor.FieldSnapshot> snapshots = List.of();
|
||||
Object target = invocation.getTarget();
|
||||
if (target instanceof ParameterHandler parameterHandler) {
|
||||
Object parameterObject = parameterHandler.getParameterObject();
|
||||
if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {
|
||||
snapshots = encryptedFieldProcessor.encrypt(parameterObject);
|
||||
}
|
||||
}
|
||||
try {
|
||||
return invocation.proceed();
|
||||
} finally {
|
||||
for (EncryptedFieldProcessor.FieldSnapshot snapshot : snapshots) {
|
||||
snapshot.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装 MyBatis 目标对象。
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @return 包装后的对象
|
||||
*/
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
return Plugin.wrap(target, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置插件属性。
|
||||
*
|
||||
* @param properties 插件属性
|
||||
*/
|
||||
@Override
|
||||
public void setProperties(Properties properties) {
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package org.dromara.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 = "encrypt-key";
|
||||
|
||||
/**
|
||||
* 响应加密公钥
|
||||
*/
|
||||
private String publicKey;
|
||||
|
||||
/**
|
||||
* 请求解密私钥
|
||||
*/
|
||||
private String privateKey;
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package org.dromara.common.encrypt.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.common.encrypt.enums.AlgorithmType;
|
||||
import org.dromara.common.encrypt.enums.EncodeType;
|
||||
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 = AlgorithmType.BASE64;
|
||||
|
||||
/**
|
||||
* 安全秘钥
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 公钥
|
||||
*/
|
||||
private String publicKey;
|
||||
|
||||
/**
|
||||
* 私钥
|
||||
*/
|
||||
private String privateKey;
|
||||
|
||||
/**
|
||||
* 编码方式,base64/hex
|
||||
*/
|
||||
private EncodeType encode = EncodeType.BASE64;
|
||||
|
||||
}
|
||||
+426
@@ -0,0 +1,426 @@
|
||||
package org.dromara.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.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.interfaces.RSAKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 安全相关工具类
|
||||
*
|
||||
* @author 老马
|
||||
*/
|
||||
public class EncryptUtils {
|
||||
|
||||
/**
|
||||
* RSA密钥最小位数
|
||||
*/
|
||||
public static final int MIN_RSA_KEY_SIZE = 1024;
|
||||
|
||||
/**
|
||||
* 公钥
|
||||
*/
|
||||
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加密(Base64编码)
|
||||
*
|
||||
* @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加密(Hex编码)
|
||||
*
|
||||
* @param data 待加密数据
|
||||
* @param password 秘钥字符串
|
||||
* @return 加密后字符串, 采用Hex编码
|
||||
*/
|
||||
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 待解密数据(可以是Base64或Hex编码)
|
||||
* @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<String, String> generateSm2Key() {
|
||||
Map<String, String> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* SM2公钥验签(Base64编码)
|
||||
*
|
||||
* @param data 原文数据
|
||||
* @param sign 签名值
|
||||
* @param publicKey 公钥
|
||||
* @return true-验签成功,false-验签失败
|
||||
*/
|
||||
public static boolean verifySm2Sign(String data, String sign, String publicKey) {
|
||||
if (StrUtil.isBlank(data)) {
|
||||
throw new IllegalArgumentException("SM2验签需要传入原文数据");
|
||||
}
|
||||
if (StrUtil.isBlank(sign)) {
|
||||
throw new IllegalArgumentException("SM2验签需要传入签名值");
|
||||
}
|
||||
if (StrUtil.isBlank(publicKey)) {
|
||||
throw new IllegalArgumentException("SM2验签需要传入公钥");
|
||||
}
|
||||
SM2 sm2 = SmUtil.sm2(null, publicKey);
|
||||
return sm2.verify(data.getBytes(StandardCharsets.UTF_8), sign.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* SM2公钥验签(Hex编码)
|
||||
*
|
||||
* @param dataHex 原文数据(Hex编码)
|
||||
* @param signHex 签名值(Hex编码)
|
||||
* @param publicKey 公钥
|
||||
* @return true-验签成功,false-验签失败
|
||||
*/
|
||||
public static boolean verifySm2SignHex(String dataHex, String signHex, String publicKey) {
|
||||
if (StrUtil.isBlank(dataHex)) {
|
||||
throw new IllegalArgumentException("SM2验签需要传入Hex格式的原文数据");
|
||||
}
|
||||
if (StrUtil.isBlank(signHex)) {
|
||||
throw new IllegalArgumentException("SM2验签需要传入Hex格式的签名值");
|
||||
}
|
||||
if (StrUtil.isBlank(publicKey)) {
|
||||
throw new IllegalArgumentException("SM2验签需要传入公钥");
|
||||
}
|
||||
SM2 sm2 = SmUtil.sm2(null, publicKey);
|
||||
return sm2.verifyHex(dataHex, signHex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 产生RSA加解密需要的公钥和私钥
|
||||
*
|
||||
* @return 公私钥Map
|
||||
*/
|
||||
public static Map<String, String> generateRsaKey() {
|
||||
try {
|
||||
Map<String, String> keyMap = new HashMap<>(2);
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGenerator.initialize(MIN_RSA_KEY_SIZE);
|
||||
KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
||||
keyMap.put(PRIVATE_KEY, java.util.Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()));
|
||||
keyMap.put(PUBLIC_KEY, java.util.Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()));
|
||||
return keyMap;
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IllegalStateException("生成RSA密钥失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* rsa公钥加密
|
||||
*
|
||||
* @param data 待加密数据
|
||||
* @param publicKey 公钥
|
||||
* @return 加密后字符串, 采用Base64编码
|
||||
*/
|
||||
public static String encryptByRsa(String data, String publicKey) {
|
||||
if (StrUtil.isBlank(publicKey)) {
|
||||
throw new IllegalArgumentException("RSA需要传入公钥进行加密");
|
||||
}
|
||||
validateRsaPublicKey(publicKey);
|
||||
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需要传入公钥进行加密");
|
||||
}
|
||||
validateRsaPublicKey(publicKey);
|
||||
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需要传入私钥进行解密");
|
||||
}
|
||||
validateRsaPrivateKey(privateKey);
|
||||
RSA rsa = SecureUtil.rsa(privateKey, null);
|
||||
return rsa.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验RSA公钥最低位数
|
||||
*
|
||||
* @param publicKey 公钥
|
||||
*/
|
||||
public static void validateRsaPublicKey(String publicKey) {
|
||||
if (StrUtil.isBlank(publicKey)) {
|
||||
throw new IllegalArgumentException("RSA需要传入公钥");
|
||||
}
|
||||
try {
|
||||
byte[] keyBytes = java.util.Base64.getDecoder().decode(publicKey);
|
||||
RSAKey rsaKey = (RSAKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(keyBytes));
|
||||
validateRsaKeySize(rsaKey);
|
||||
} catch (IllegalArgumentException | GeneralSecurityException e) {
|
||||
throw new IllegalArgumentException("RSA公钥格式错误或密钥长度低于" + MIN_RSA_KEY_SIZE + "位", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验RSA私钥最低位数
|
||||
*
|
||||
* @param privateKey 私钥
|
||||
*/
|
||||
public static void validateRsaPrivateKey(String privateKey) {
|
||||
if (StrUtil.isBlank(privateKey)) {
|
||||
throw new IllegalArgumentException("RSA需要传入私钥");
|
||||
}
|
||||
try {
|
||||
byte[] keyBytes = java.util.Base64.getDecoder().decode(privateKey);
|
||||
RSAKey rsaKey = (RSAKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
|
||||
validateRsaKeySize(rsaKey);
|
||||
} catch (IllegalArgumentException | GeneralSecurityException e) {
|
||||
throw new IllegalArgumentException("RSA私钥格式错误或密钥长度低于" + MIN_RSA_KEY_SIZE + "位", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 RSA 密钥长度是否满足最低安全要求。
|
||||
*
|
||||
* @param rsaKey RSA 密钥
|
||||
*/
|
||||
private static void validateRsaKeySize(RSAKey rsaKey) {
|
||||
int keySize = rsaKey.getModulus().bitLength();
|
||||
if (keySize < MIN_RSA_KEY_SIZE) {
|
||||
throw new IllegalArgumentException("RSA密钥长度不能低于" + MIN_RSA_KEY_SIZE + "位,当前为" + keySize + "位");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
org.dromara.common.encrypt.config.EncryptorAutoConfiguration
|
||||
org.dromara.common.encrypt.config.ApiDecryptAutoConfiguration
|
||||
|
||||
Reference in New Issue
Block a user