mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-09-08 16:57:09 +08:00
refactor(security/crypto):重构加/解密模块业务逻辑,封装 EncryptHelper 工具类,提供统一的加/解密方法,方便使用者灵活处理加/解密
This commit is contained in:
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.core.util;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
@@ -70,7 +86,10 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
* @param nodeParser 解析器,用于将输入节点转换为树节点
|
||||
* @return 构建好的树形结构列表
|
||||
*/
|
||||
public static <T, K> List<Tree<K>> build(List<T> list, K parentId, TreeNodeConfig treeNodeConfig, NodeParser<T, K> nodeParser) {
|
||||
public static <T, K> List<Tree<K>> build(List<T> list,
|
||||
K parentId,
|
||||
TreeNodeConfig treeNodeConfig,
|
||||
NodeParser<T, K> nodeParser) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
@@ -88,7 +107,10 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
* @param <K> 节点 ID 类型(如 Long、String)
|
||||
* @return 构建完成的树形结构(可能包含多个顶级根节点)
|
||||
*/
|
||||
public static <T, K> List<Tree<K>> buildMultiRoot(List<T> list, Function<T, K> getId, Function<T, K> getParentId, NodeParser<T, K> parser) {
|
||||
public static <T, K> List<Tree<K>> buildMultiRoot(List<T> list,
|
||||
Function<T, K> getId,
|
||||
Function<T, K> getParentId,
|
||||
NodeParser<T, K> parser) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
@@ -112,8 +134,11 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
* @param parser 树节点属性映射器,用于将原始节点 T 转为 Tree 节点
|
||||
* @return 构建完成的树形结构(可能包含多个顶级根节点)
|
||||
*/
|
||||
public static <T, K> List<Tree<K>> buildMultiRoot(List<T> list, Function<T, K> getId, Function<T, K> getParentId,
|
||||
TreeNodeConfig treeNodeConfig, NodeParser<T, K> parser) {
|
||||
public static <T, K> List<Tree<K>> buildMultiRoot(List<T> list,
|
||||
Function<T, K> getId,
|
||||
Function<T, K> getParentId,
|
||||
TreeNodeConfig treeNodeConfig,
|
||||
NodeParser<T, K> parser) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
@@ -121,8 +146,8 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
rootParentIds.removeAll(CollUtils.mapToSet(list, getId));
|
||||
// 构建每一个根 parentId 下的树,并合并成最终结果列表
|
||||
return rootParentIds.stream()
|
||||
.flatMap(rootParentId -> TreeUtil.build(list, rootParentId, treeNodeConfig, parser).stream())
|
||||
.collect(Collectors.toList());
|
||||
.flatMap(rootParentId -> TreeUtil.build(list, rootParentId, treeNodeConfig, parser).stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,9 +161,7 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
if (CollUtil.isEmpty(nodes)) {
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
return nodes.stream()
|
||||
.flatMap(TreeBuildUtils::extractLeafNodes)
|
||||
.collect(Collectors.toList());
|
||||
return nodes.stream().flatMap(TreeBuildUtils::extractLeafNodes).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,8 +176,7 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
return Stream.of(node);
|
||||
} else {
|
||||
// 递归调用,获取所有子节点的叶子节点
|
||||
return node.getChildren().stream()
|
||||
.flatMap(TreeBuildUtils::extractLeafNodes);
|
||||
return node.getChildren().stream().flatMap(TreeBuildUtils::extractLeafNodes);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -105,7 +105,9 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
CrudTreeProperties treeProperties = crudProperties.getTree();
|
||||
TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class);
|
||||
// 简单树(下拉列表)使用全局配置结构,复杂树(表格)使用局部配置
|
||||
TreeNodeConfig treeNodeConfig = isSimple ? treeProperties.genTreeNodeConfig() : treeProperties.genTreeNodeConfig(treeField);
|
||||
TreeNodeConfig treeNodeConfig = isSimple
|
||||
? treeProperties.genTreeNodeConfig()
|
||||
: treeProperties.genTreeNodeConfig(treeField);
|
||||
String valueGetter = CharSequenceUtil.genGetter(treeField.value());
|
||||
String parentIdKeyGetter = CharSequenceUtil.genGetter(treeField.parentIdKey());
|
||||
Function<L, Long> getId = createMethodReference(listClass, valueGetter);
|
||||
|
@@ -28,6 +28,7 @@ import java.lang.annotation.Target;
|
||||
* 字段加/解密注解
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.PARAMETER})
|
||||
@@ -37,7 +38,7 @@ public @interface FieldEncrypt {
|
||||
/**
|
||||
* 加密/解密算法
|
||||
*/
|
||||
Algorithm value() default Algorithm.AES;
|
||||
Algorithm value() default Algorithm.DEFAULT;
|
||||
|
||||
/**
|
||||
* 加密/解密处理器
|
||||
@@ -51,4 +52,14 @@ public @interface FieldEncrypt {
|
||||
* 对称加密算法密钥
|
||||
*/
|
||||
String password() default "";
|
||||
|
||||
/**
|
||||
* 非对称加密算法公钥:RSA需要
|
||||
*/
|
||||
String publicKey() default "";
|
||||
|
||||
/**
|
||||
* 非对称加密算法私钥:RSA需要
|
||||
*/
|
||||
String privateKey() default "";
|
||||
}
|
@@ -27,11 +27,13 @@ import org.springframework.context.annotation.Bean;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.security.crypto.core.MyBatisDecryptInterceptor;
|
||||
import top.continew.starter.security.crypto.core.MyBatisEncryptInterceptor;
|
||||
import top.continew.starter.security.crypto.utils.EncryptHelper;
|
||||
|
||||
/**
|
||||
* 加/解密自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@@ -52,7 +54,7 @@ public class CryptoAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public MyBatisEncryptInterceptor myBatisEncryptInterceptor() {
|
||||
return new MyBatisEncryptInterceptor(properties);
|
||||
return new MyBatisEncryptInterceptor();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,11 +63,12 @@ public class CryptoAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(MyBatisDecryptInterceptor.class)
|
||||
public MyBatisDecryptInterceptor myBatisDecryptInterceptor() {
|
||||
return new MyBatisDecryptInterceptor(properties);
|
||||
return new MyBatisDecryptInterceptor();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
EncryptHelper.init(properties);
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Security-Crypto' completed initialization.");
|
||||
}
|
||||
}
|
||||
|
@@ -18,11 +18,13 @@ package top.continew.starter.security.crypto.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.security.crypto.enums.Algorithm;
|
||||
|
||||
/**
|
||||
* 加/解密配置属性
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.SECURITY_CRYPTO)
|
||||
@@ -33,6 +35,11 @@ public class CryptoProperties {
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 默认算法
|
||||
*/
|
||||
private Algorithm algorithm = Algorithm.AES;
|
||||
|
||||
/**
|
||||
* 对称加密算法密钥
|
||||
*/
|
||||
@@ -56,6 +63,14 @@ public class CryptoProperties {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Algorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public void setAlgorithm(Algorithm algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
@@ -18,14 +18,11 @@ package top.continew.starter.security.crypto.core;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.exception.BaseException;
|
||||
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
||||
import top.continew.starter.security.crypto.encryptor.IEncryptor;
|
||||
import top.continew.starter.security.crypto.enums.Algorithm;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -71,23 +68,6 @@ public abstract class AbstractMyBatisInterceptor {
|
||||
.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段加/解密处理器
|
||||
*
|
||||
* @param fieldEncrypt 字段加密注解
|
||||
* @return 加/解密处理器
|
||||
*/
|
||||
protected IEncryptor getEncryptor(FieldEncrypt fieldEncrypt) {
|
||||
Class<? extends IEncryptor> encryptorClass = fieldEncrypt.encryptor();
|
||||
// 使用预定义加/解密处理器
|
||||
if (encryptorClass == IEncryptor.class) {
|
||||
Algorithm algorithm = fieldEncrypt.value();
|
||||
return ReflectUtil.newInstance(algorithm.getEncryptor());
|
||||
}
|
||||
// 使用自定义加/解密处理器
|
||||
return SpringUtil.getBean(encryptorClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取加密参数
|
||||
*
|
||||
|
@@ -17,6 +17,7 @@
|
||||
package top.continew.starter.security.crypto.core;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import org.apache.ibatis.executor.resultset.ResultSetHandler;
|
||||
@@ -25,39 +26,30 @@ import org.apache.ibatis.plugin.Intercepts;
|
||||
import org.apache.ibatis.plugin.Invocation;
|
||||
import org.apache.ibatis.plugin.Signature;
|
||||
import org.apache.ibatis.type.SimpleTypeRegistry;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
|
||||
import top.continew.starter.security.crypto.encryptor.IEncryptor;
|
||||
import top.continew.starter.security.crypto.utils.EncryptHelper;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.sql.Statement;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 字段解密拦截器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
|
||||
public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implements Interceptor {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MyBatisDecryptInterceptor.class);
|
||||
private CryptoProperties properties;
|
||||
|
||||
public MyBatisDecryptInterceptor(CryptoProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public MyBatisDecryptInterceptor() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
// 获取执行结果
|
||||
Object obj = invocation.proceed();
|
||||
if (obj == null) {
|
||||
if (ObjectUtil.isNull(obj)) {
|
||||
return null;
|
||||
}
|
||||
// 确保目标是 ResultSetHandler
|
||||
@@ -68,6 +60,9 @@ public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implem
|
||||
if (obj instanceof List<?> resultList) {
|
||||
// 处理列表结果
|
||||
this.decryptList(resultList);
|
||||
} else if (obj instanceof Map<?, ?> map) {
|
||||
// 处理Map结果
|
||||
this.decryptMap(map);
|
||||
} else {
|
||||
// 处理单个对象结果
|
||||
this.decryptObject(obj);
|
||||
@@ -89,13 +84,25 @@ public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implem
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密Map结果
|
||||
*
|
||||
* @param resultMap 结果Map
|
||||
*/
|
||||
private void decryptMap(Map<?, ?> resultMap) {
|
||||
if (CollUtil.isEmpty(resultMap)) {
|
||||
return;
|
||||
}
|
||||
new HashSet<>(resultMap.values()).forEach(this::decryptObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密单个对象结果
|
||||
*
|
||||
* @param result 结果对象
|
||||
*/
|
||||
private void decryptObject(Object result) {
|
||||
if (result == null) {
|
||||
if (ObjectUtil.isNull(result)) {
|
||||
return;
|
||||
}
|
||||
// String、Integer、Long 等简单类型对象无需处理
|
||||
@@ -109,21 +116,16 @@ public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implem
|
||||
}
|
||||
// 解密处理
|
||||
for (Field field : fieldList) {
|
||||
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
|
||||
Object fieldValue = ReflectUtil.getFieldValue(result, field);
|
||||
if (fieldValue == null) {
|
||||
continue;
|
||||
}
|
||||
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
|
||||
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class).password(), properties
|
||||
.getPassword());
|
||||
try {
|
||||
String ciphertext = encryptor.decrypt(fieldValue.toString(), password, properties.getPrivateKey());
|
||||
ReflectUtil.setFieldValue(result, field, ciphertext);
|
||||
} catch (Exception e) {
|
||||
// 解密失败时保留原值,避免影响正常业务流程
|
||||
log.warn("解密失败,请检查加密配置", e);
|
||||
String strValue = String.valueOf(fieldValue);
|
||||
if (CharSequenceUtil.isBlank(strValue)) {
|
||||
continue;
|
||||
}
|
||||
ReflectUtil.setFieldValue(result, field, EncryptHelper.decrypt(strValue, field
|
||||
.getAnnotation(FieldEncrypt.class)));
|
||||
}
|
||||
}
|
||||
}
|
@@ -28,12 +28,9 @@ import org.apache.ibatis.mapping.BoundSql;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.session.ResultHandler;
|
||||
import org.apache.ibatis.session.RowBounds;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
|
||||
import top.continew.starter.security.crypto.encryptor.IEncryptor;
|
||||
import top.continew.starter.security.crypto.utils.EncryptHelper;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
@@ -47,18 +44,13 @@ import java.util.regex.Pattern;
|
||||
* 字段加密拦截器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implements InnerInterceptor {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MyBatisEncryptInterceptor.class);
|
||||
private static final Pattern PARAM_PAIRS_PATTERN = Pattern
|
||||
.compile("#\\{ew\\.paramNameValuePairs\\.(" + Constants.WRAPPER_PARAM + "\\d+)\\}");
|
||||
private final CryptoProperties properties;
|
||||
|
||||
public MyBatisEncryptInterceptor(CryptoProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeQuery(Executor executor,
|
||||
@@ -124,22 +116,16 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
|
||||
*/
|
||||
private void encryptEntity(List<Field> fieldList, Object entity) {
|
||||
for (Field field : fieldList) {
|
||||
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
|
||||
Object fieldValue = ReflectUtil.getFieldValue(entity, field);
|
||||
if (fieldValue == null) {
|
||||
continue;
|
||||
}
|
||||
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
|
||||
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class).password(), properties
|
||||
.getPassword());
|
||||
String ciphertext = fieldValue.toString();
|
||||
try {
|
||||
ciphertext = encryptor.encrypt(fieldValue.toString(), password, properties.getPublicKey());
|
||||
} catch (Exception e) {
|
||||
// 加密失败时保留原值,避免影响正常业务流程
|
||||
log.warn("加密失败,请检查加密配置", e);
|
||||
String strValue = String.valueOf(fieldValue);
|
||||
if (CharSequenceUtil.isBlank(strValue)) {
|
||||
continue;
|
||||
}
|
||||
ReflectUtil.setFieldValue(entity, field, ciphertext);
|
||||
ReflectUtil.setFieldValue(entity, field, EncryptHelper.encrypt(strValue, field
|
||||
.getAnnotation(FieldEncrypt.class)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,8 +183,7 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
|
||||
if (matcher.matches()) {
|
||||
String valueKey = matcher.group(1);
|
||||
Object value = updateWrapper.getParamNameValuePairs().get(valueKey);
|
||||
Object ciphertext = this.doEncrypt(value, fieldEncrypt);
|
||||
updateWrapper.getParamNameValuePairs().put(valueKey, ciphertext);
|
||||
updateWrapper.getParamNameValuePairs().put(valueKey, this.doEncrypt(value, fieldEncrypt));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,19 +196,14 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
|
||||
* @param fieldEncrypt 字段加密注解
|
||||
*/
|
||||
private Object doEncrypt(Object parameterValue, FieldEncrypt fieldEncrypt) {
|
||||
if (parameterValue == null) {
|
||||
if (ObjectUtil.isNull(parameterValue)) {
|
||||
return null;
|
||||
}
|
||||
IEncryptor encryptor = super.getEncryptor(fieldEncrypt);
|
||||
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
|
||||
String password = ObjectUtil.defaultIfBlank(fieldEncrypt.password(), properties.getPassword());
|
||||
try {
|
||||
return encryptor.encrypt(parameterValue.toString(), password, properties.getPublicKey());
|
||||
} catch (Exception e) {
|
||||
// 加密失败时保留原值,避免影响正常业务流程
|
||||
log.warn("加密失败,请检查加密配置", e);
|
||||
String strValue = String.valueOf(parameterValue);
|
||||
if (CharSequenceUtil.isBlank(strValue)) {
|
||||
return null;
|
||||
}
|
||||
return parameterValue;
|
||||
return EncryptHelper.encrypt(strValue, fieldEncrypt);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.security.crypto.encryptor;
|
||||
|
||||
/**
|
||||
* 加密器基类
|
||||
*
|
||||
* @author lishuyan
|
||||
*/
|
||||
public abstract class AbstractEncryptor implements IEncryptor {
|
||||
|
||||
public AbstractEncryptor(CryptoContext context) {
|
||||
// 配置校验与配置注入
|
||||
}
|
||||
|
||||
}
|
@@ -29,26 +29,40 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* 对称加/解密处理器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public abstract class AbstractSymmetricCryptoEncryptor implements IEncryptor {
|
||||
public abstract class AbstractSymmetricCryptoEncryptor extends AbstractEncryptor {
|
||||
|
||||
/**
|
||||
* 对称加密缓存
|
||||
*/
|
||||
private static final Map<String, SymmetricCrypto> CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public String encrypt(String plaintext, String password, String publicKey) throws Exception {
|
||||
if (CharSequenceUtil.isBlank(plaintext)) {
|
||||
return plaintext;
|
||||
}
|
||||
return this.getCrypto(password).encryptHex(plaintext);
|
||||
/**
|
||||
* 加密上下文
|
||||
*/
|
||||
private final CryptoContext context;
|
||||
|
||||
public AbstractSymmetricCryptoEncryptor(CryptoContext context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String ciphertext, String password, String privateKey) throws Exception {
|
||||
public String encrypt(String plaintext) {
|
||||
if (CharSequenceUtil.isBlank(plaintext)) {
|
||||
return plaintext;
|
||||
}
|
||||
return this.getCrypto(context.getPassword()).encryptHex(plaintext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String ciphertext) {
|
||||
if (CharSequenceUtil.isBlank(ciphertext)) {
|
||||
return ciphertext;
|
||||
}
|
||||
return this.getCrypto(password).decryptStr(ciphertext);
|
||||
return this.getCrypto(context.getPassword()).decryptStr(ciphertext);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -29,6 +29,10 @@ import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||
*/
|
||||
public class AesEncryptor extends AbstractSymmetricCryptoEncryptor {
|
||||
|
||||
public AesEncryptor(CryptoContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SymmetricAlgorithm getAlgorithm() {
|
||||
return SymmetricAlgorithm.AES;
|
||||
|
@@ -30,12 +30,12 @@ import cn.hutool.core.codec.Base64;
|
||||
public class Base64Encryptor implements IEncryptor {
|
||||
|
||||
@Override
|
||||
public String encrypt(String plaintext, String password, String publicKey) throws Exception {
|
||||
public String encrypt(String plaintext) {
|
||||
return Base64.encode(plaintext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String ciphertext, String password, String privateKey) throws Exception {
|
||||
public String decrypt(String ciphertext) {
|
||||
return Base64.decodeStr(ciphertext);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.security.crypto.encryptor;
|
||||
|
||||
import top.continew.starter.security.crypto.enums.Algorithm;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 加密上下文
|
||||
*
|
||||
* @author lishuyan
|
||||
*/
|
||||
public class CryptoContext {
|
||||
|
||||
/**
|
||||
* 默认算法
|
||||
*/
|
||||
private Algorithm algorithm;
|
||||
|
||||
/**
|
||||
* 对称加密算法密钥
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 非对称加密算法公钥
|
||||
*/
|
||||
private String publicKey;
|
||||
|
||||
/**
|
||||
* 非对称加密算法私钥
|
||||
*/
|
||||
private String privateKey;
|
||||
|
||||
public Algorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public void setAlgorithm(Algorithm algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public void setPublicKey(String publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public String getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public void setPrivateKey(String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CryptoContext that = (CryptoContext)o;
|
||||
return algorithm == that.algorithm && Objects.equals(password, that.password) && Objects
|
||||
.equals(publicKey, that.publicKey) && Objects.equals(privateKey, that.privateKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(algorithm, password, publicKey, privateKey);
|
||||
}
|
||||
}
|
@@ -29,6 +29,10 @@ import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||
*/
|
||||
public class DesEncryptor extends AbstractSymmetricCryptoEncryptor {
|
||||
|
||||
public DesEncryptor(CryptoContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SymmetricAlgorithm getAlgorithm() {
|
||||
return SymmetricAlgorithm.DES;
|
||||
|
@@ -20,6 +20,7 @@ package top.continew.starter.security.crypto.encryptor;
|
||||
* 加/解密接口
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public interface IEncryptor {
|
||||
@@ -28,21 +29,15 @@ public interface IEncryptor {
|
||||
* 加密
|
||||
*
|
||||
* @param plaintext 明文
|
||||
* @param password 对称加密算法密钥
|
||||
* @param publicKey 非对称加密算法公钥
|
||||
* @return 加密后的文本
|
||||
* @throws Exception /
|
||||
*/
|
||||
String encrypt(String plaintext, String password, String publicKey) throws Exception;
|
||||
String encrypt(String plaintext);
|
||||
|
||||
/**
|
||||
* 解密
|
||||
*
|
||||
* @param ciphertext 密文
|
||||
* @param password 对称加密算法密钥
|
||||
* @param privateKey 非对称加密算法私钥
|
||||
* @return 解密后的文本
|
||||
* @throws Exception /
|
||||
*/
|
||||
String decrypt(String ciphertext, String password, String privateKey) throws Exception;
|
||||
String decrypt(String ciphertext);
|
||||
}
|
||||
|
@@ -29,6 +29,10 @@ import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||
*/
|
||||
public class PbeWithMd5AndDesEncryptor extends AbstractSymmetricCryptoEncryptor {
|
||||
|
||||
public PbeWithMd5AndDesEncryptor(CryptoContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SymmetricAlgorithm getAlgorithm() {
|
||||
return SymmetricAlgorithm.PBEWithMD5AndDES;
|
||||
|
@@ -27,17 +27,29 @@ import cn.hutool.crypto.asymmetric.KeyType;
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class RsaEncryptor implements IEncryptor {
|
||||
public class RsaEncryptor extends AbstractEncryptor {
|
||||
|
||||
@Override
|
||||
public String encrypt(String plaintext, String password, String publicKey) throws Exception {
|
||||
return Base64.encode(SecureUtil.rsa(null, publicKey).encrypt(plaintext, KeyType.PublicKey));
|
||||
/**
|
||||
* 加密上下文
|
||||
*/
|
||||
private final CryptoContext context;
|
||||
|
||||
public RsaEncryptor(CryptoContext context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String ciphertext, String password, String privateKey) throws Exception {
|
||||
return new String(SecureUtil.rsa(privateKey, null).decrypt(Base64.decode(ciphertext), KeyType.PrivateKey));
|
||||
public String encrypt(String plaintext) {
|
||||
return Base64.encode(SecureUtil.rsa(null, context.getPublicKey()).encrypt(plaintext, KeyType.PublicKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String ciphertext) {
|
||||
return new String(SecureUtil.rsa(context.getPrivateKey(), null)
|
||||
.decrypt(Base64.decode(ciphertext), KeyType.PrivateKey));
|
||||
}
|
||||
}
|
||||
|
@@ -22,10 +22,16 @@ import top.continew.starter.security.crypto.encryptor.*;
|
||||
* 加密/解密算法枚举
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public enum Algorithm {
|
||||
|
||||
/**
|
||||
* 默认使用配置属性的算法
|
||||
*/
|
||||
DEFAULT(null),
|
||||
|
||||
/**
|
||||
* AES
|
||||
*/
|
||||
|
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.security.crypto.utils;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
|
||||
import top.continew.starter.security.crypto.encryptor.CryptoContext;
|
||||
import top.continew.starter.security.crypto.encryptor.IEncryptor;
|
||||
import top.continew.starter.security.crypto.enums.Algorithm;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 加密助手
|
||||
*
|
||||
* @author lishuyan
|
||||
*/
|
||||
public class EncryptHelper {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(EncryptHelper.class);
|
||||
|
||||
/**
|
||||
* 默认加密配置
|
||||
*/
|
||||
private static CryptoProperties defaultProperties;
|
||||
|
||||
/**
|
||||
* 加密器缓存
|
||||
*/
|
||||
private static final Map<Integer, IEncryptor> ENCRYPTOR_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 初始化默认配置
|
||||
*
|
||||
* @param properties 加密配置
|
||||
*/
|
||||
public static void init(CryptoProperties properties) {
|
||||
defaultProperties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册加密执行者到缓存
|
||||
* <p>
|
||||
* 计算 CryptoContext 对象的hashCode作为缓存中的key,通过hashCode查询缓存中存在则直接返回,不存在则创建并缓存
|
||||
* </p>
|
||||
*
|
||||
* @param encryptContext 加密执行者需要的相关配置参数
|
||||
* @return 加密执行者
|
||||
*/
|
||||
public static IEncryptor registerAndGetEncryptor(CryptoContext encryptContext) {
|
||||
int key = encryptContext.hashCode();
|
||||
return ENCRYPTOR_CACHE.computeIfAbsent(key, k -> ReflectUtil.newInstance(encryptContext.getAlgorithm()
|
||||
.getEncryptor(), encryptContext));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段上的 @FieldEncrypt 注解
|
||||
*
|
||||
* @param obj 对象
|
||||
* @param fieldName 字段名称
|
||||
* @return 字段上的 @FieldEncrypt 注解
|
||||
* @throws NoSuchFieldException /
|
||||
*/
|
||||
public static FieldEncrypt getFieldEncrypt(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Field field = obj.getClass().getDeclaredField(fieldName);
|
||||
return field.getAnnotation(FieldEncrypt.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密方法
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @param fieldEncrypt 待加密字段注解
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
public static String encrypt(String value, FieldEncrypt fieldEncrypt) {
|
||||
if (CharSequenceUtil.isBlank(value) || fieldEncrypt == null || !defaultProperties.isEnabled()) {
|
||||
return value;
|
||||
}
|
||||
String ciphertext = value;
|
||||
try {
|
||||
CryptoContext encryptContext = buildEncryptContext(fieldEncrypt);
|
||||
IEncryptor encryptor = registerAndGetEncryptor(encryptContext);
|
||||
ciphertext = encryptor.encrypt(ciphertext);
|
||||
} catch (Exception e) {
|
||||
log.warn("加密失败,请检查加密配置,处理加密字段异常:{}", e.getMessage(), e);
|
||||
}
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密方法
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
public static String encrypt(String value) {
|
||||
if (CharSequenceUtil.isBlank(value) || !defaultProperties.isEnabled()) {
|
||||
return value;
|
||||
}
|
||||
String ciphertext = value;
|
||||
try {
|
||||
CryptoContext encryptContext = buildEncryptContext();
|
||||
IEncryptor encryptor = registerAndGetEncryptor(encryptContext);
|
||||
ciphertext = encryptor.encrypt(ciphertext);
|
||||
} catch (Exception e) {
|
||||
log.warn("加密失败,请检查加密配置,处理加密字段异常:{}", e.getMessage(), e);
|
||||
}
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密方法
|
||||
*
|
||||
* @param value 待解密字符串
|
||||
* @param fieldEncrypt 待解密字段注解
|
||||
* @return 解密后的字符串
|
||||
*/
|
||||
public static String decrypt(String value, FieldEncrypt fieldEncrypt) {
|
||||
if (CharSequenceUtil.isBlank(value) || fieldEncrypt == null || !defaultProperties.isEnabled()) {
|
||||
return value;
|
||||
}
|
||||
String plaintext = value;
|
||||
try {
|
||||
CryptoContext encryptContext = buildEncryptContext(fieldEncrypt);
|
||||
IEncryptor encryptor = registerAndGetEncryptor(encryptContext);
|
||||
plaintext = encryptor.decrypt(plaintext);
|
||||
} catch (Exception e) {
|
||||
log.warn("解密失败,请检查加密配置,处理解密字段异常:{}", e.getMessage(), e);
|
||||
}
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密方法
|
||||
*
|
||||
* @param value 待解密字符串
|
||||
* @return 解密后的字符串
|
||||
*/
|
||||
public static String decrypt(String value) {
|
||||
if (CharSequenceUtil.isBlank(value) || !defaultProperties.isEnabled()) {
|
||||
return value;
|
||||
}
|
||||
String plaintext = value;
|
||||
try {
|
||||
CryptoContext encryptContext = buildEncryptContext();
|
||||
IEncryptor encryptor = registerAndGetEncryptor(encryptContext);
|
||||
plaintext = encryptor.decrypt(plaintext);
|
||||
} catch (Exception e) {
|
||||
log.warn("解密失败,请检查加密配置,处理解密字段异常:{}", e.getMessage(), e);
|
||||
}
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建加密上下文
|
||||
*
|
||||
* @param fieldEncrypt 字段加密注解
|
||||
* @return 加密上下文
|
||||
*/
|
||||
private static CryptoContext buildEncryptContext(FieldEncrypt fieldEncrypt) {
|
||||
CryptoContext encryptContext = new CryptoContext();
|
||||
encryptContext.setAlgorithm(fieldEncrypt.value() == Algorithm.DEFAULT
|
||||
? defaultProperties.getAlgorithm()
|
||||
: fieldEncrypt.value());
|
||||
encryptContext.setPassword(fieldEncrypt.password().isEmpty()
|
||||
? defaultProperties.getPassword()
|
||||
: fieldEncrypt.password());
|
||||
encryptContext.setPrivateKey(fieldEncrypt.privateKey().isEmpty()
|
||||
? defaultProperties.getPrivateKey()
|
||||
: fieldEncrypt.privateKey());
|
||||
encryptContext.setPublicKey(fieldEncrypt.publicKey().isEmpty()
|
||||
? defaultProperties.getPublicKey()
|
||||
: fieldEncrypt.publicKey());
|
||||
return encryptContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建加密上下文
|
||||
*
|
||||
* @return 加密上下文
|
||||
*/
|
||||
private static CryptoContext buildEncryptContext() {
|
||||
CryptoContext encryptContext = new CryptoContext();
|
||||
encryptContext.setAlgorithm(defaultProperties.getAlgorithm());
|
||||
encryptContext.setPassword(defaultProperties.getPassword());
|
||||
encryptContext.setPrivateKey(defaultProperties.getPrivateKey());
|
||||
encryptContext.setPublicKey(defaultProperties.getPublicKey());
|
||||
return encryptContext;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user