refactor(security/crypto):重构加/解密模块业务逻辑,封装 EncryptHelper 工具类,提供统一的加/解密方法,方便使用者灵活处理加/解密

This commit is contained in:
书中自有颜如玉
2025-07-21 10:14:25 +00:00
committed by Charles7c
parent c8e5191dc0
commit 5d10a28aa1
19 changed files with 513 additions and 119 deletions

View File

@@ -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; package top.continew.starter.core.util;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
@@ -70,7 +86,10 @@ public class TreeBuildUtils extends TreeUtil {
* @param nodeParser 解析器,用于将输入节点转换为树节点 * @param nodeParser 解析器,用于将输入节点转换为树节点
* @return 构建好的树形结构列表 * @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)) { if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList(); return CollUtil.newArrayList();
} }
@@ -88,7 +107,10 @@ public class TreeBuildUtils extends TreeUtil {
* @param <K> 节点 ID 类型(如 Long、String * @param <K> 节点 ID 类型(如 Long、String
* @return 构建完成的树形结构(可能包含多个顶级根节点) * @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)) { if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList(); return CollUtil.newArrayList();
} }
@@ -112,8 +134,11 @@ public class TreeBuildUtils extends TreeUtil {
* @param parser 树节点属性映射器,用于将原始节点 T 转为 Tree 节点 * @param parser 树节点属性映射器,用于将原始节点 T 转为 Tree 节点
* @return 构建完成的树形结构(可能包含多个顶级根节点) * @return 构建完成的树形结构(可能包含多个顶级根节点)
*/ */
public static <T, K> List<Tree<K>> buildMultiRoot(List<T> list, Function<T, K> getId, Function<T, K> getParentId, public static <T, K> List<Tree<K>> buildMultiRoot(List<T> list,
TreeNodeConfig treeNodeConfig, NodeParser<T, K> parser) { Function<T, K> getId,
Function<T, K> getParentId,
TreeNodeConfig treeNodeConfig,
NodeParser<T, K> parser) {
if (CollUtil.isEmpty(list)) { if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList(); return CollUtil.newArrayList();
} }
@@ -121,8 +146,8 @@ public class TreeBuildUtils extends TreeUtil {
rootParentIds.removeAll(CollUtils.mapToSet(list, getId)); rootParentIds.removeAll(CollUtils.mapToSet(list, getId));
// 构建每一个根 parentId 下的树,并合并成最终结果列表 // 构建每一个根 parentId 下的树,并合并成最终结果列表
return rootParentIds.stream() return rootParentIds.stream()
.flatMap(rootParentId -> TreeUtil.build(list, rootParentId, treeNodeConfig, parser).stream()) .flatMap(rootParentId -> TreeUtil.build(list, rootParentId, treeNodeConfig, parser).stream())
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
/** /**
@@ -136,9 +161,7 @@ public class TreeBuildUtils extends TreeUtil {
if (CollUtil.isEmpty(nodes)) { if (CollUtil.isEmpty(nodes)) {
return CollUtil.newArrayList(); return CollUtil.newArrayList();
} }
return nodes.stream() return nodes.stream().flatMap(TreeBuildUtils::extractLeafNodes).collect(Collectors.toList());
.flatMap(TreeBuildUtils::extractLeafNodes)
.collect(Collectors.toList());
} }
/** /**
@@ -153,8 +176,7 @@ public class TreeBuildUtils extends TreeUtil {
return Stream.of(node); return Stream.of(node);
} else { } else {
// 递归调用,获取所有子节点的叶子节点 // 递归调用,获取所有子节点的叶子节点
return node.getChildren().stream() return node.getChildren().stream().flatMap(TreeBuildUtils::extractLeafNodes);
.flatMap(TreeBuildUtils::extractLeafNodes);
} }
} }

View File

@@ -105,7 +105,9 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
CrudTreeProperties treeProperties = crudProperties.getTree(); CrudTreeProperties treeProperties = crudProperties.getTree();
TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class); 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 valueGetter = CharSequenceUtil.genGetter(treeField.value());
String parentIdKeyGetter = CharSequenceUtil.genGetter(treeField.parentIdKey()); String parentIdKeyGetter = CharSequenceUtil.genGetter(treeField.parentIdKey());
Function<L, Long> getId = createMethodReference(listClass, valueGetter); Function<L, Long> getId = createMethodReference(listClass, valueGetter);

View File

@@ -28,6 +28,7 @@ import java.lang.annotation.Target;
* 字段加/解密注解 * 字段加/解密注解
* *
* @author Charles7c * @author Charles7c
* @author lishuyan
* @since 1.4.0 * @since 1.4.0
*/ */
@Target({ElementType.FIELD, ElementType.PARAMETER}) @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 ""; String password() default "";
/**
* 非对称加密算法公钥RSA需要
*/
String publicKey() default "";
/**
* 非对称加密算法私钥RSA需要
*/
String privateKey() default "";
} }

View File

@@ -27,11 +27,13 @@ import org.springframework.context.annotation.Bean;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.security.crypto.core.MyBatisDecryptInterceptor; import top.continew.starter.security.crypto.core.MyBatisDecryptInterceptor;
import top.continew.starter.security.crypto.core.MyBatisEncryptInterceptor; import top.continew.starter.security.crypto.core.MyBatisEncryptInterceptor;
import top.continew.starter.security.crypto.utils.EncryptHelper;
/** /**
* 加/解密自动配置 * 加/解密自动配置
* *
* @author Charles7c * @author Charles7c
* @author lishuyan
* @since 1.4.0 * @since 1.4.0
*/ */
@AutoConfiguration @AutoConfiguration
@@ -52,7 +54,7 @@ public class CryptoAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public MyBatisEncryptInterceptor myBatisEncryptInterceptor() { public MyBatisEncryptInterceptor myBatisEncryptInterceptor() {
return new MyBatisEncryptInterceptor(properties); return new MyBatisEncryptInterceptor();
} }
/** /**
@@ -61,11 +63,12 @@ public class CryptoAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(MyBatisDecryptInterceptor.class) @ConditionalOnMissingBean(MyBatisDecryptInterceptor.class)
public MyBatisDecryptInterceptor myBatisDecryptInterceptor() { public MyBatisDecryptInterceptor myBatisDecryptInterceptor() {
return new MyBatisDecryptInterceptor(properties); return new MyBatisDecryptInterceptor();
} }
@PostConstruct @PostConstruct
public void postConstruct() { public void postConstruct() {
EncryptHelper.init(properties);
log.debug("[ContiNew Starter] - Auto Configuration 'Security-Crypto' completed initialization."); log.debug("[ContiNew Starter] - Auto Configuration 'Security-Crypto' completed initialization.");
} }
} }

View File

@@ -18,11 +18,13 @@ package top.continew.starter.security.crypto.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.security.crypto.enums.Algorithm;
/** /**
* 加/解密配置属性 * 加/解密配置属性
* *
* @author Charles7c * @author Charles7c
* @author lishuyan
* @since 1.4.0 * @since 1.4.0
*/ */
@ConfigurationProperties(PropertiesConstants.SECURITY_CRYPTO) @ConfigurationProperties(PropertiesConstants.SECURITY_CRYPTO)
@@ -33,6 +35,11 @@ public class CryptoProperties {
*/ */
private boolean enabled = true; private boolean enabled = true;
/**
* 默认算法
*/
private Algorithm algorithm = Algorithm.AES;
/** /**
* 对称加密算法密钥 * 对称加密算法密钥
*/ */
@@ -56,6 +63,14 @@ public class CryptoProperties {
this.enabled = enabled; this.enabled = enabled;
} }
public Algorithm getAlgorithm() {
return algorithm;
}
public void setAlgorithm(Algorithm algorithm) {
this.algorithm = algorithm;
}
public String getPassword() { public String getPassword() {
return password; return password;
} }

View File

@@ -18,14 +18,11 @@ package top.continew.starter.security.crypto.core;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.MappedStatement;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.exception.BaseException; import top.continew.starter.core.exception.BaseException;
import top.continew.starter.security.crypto.annotation.FieldEncrypt; 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.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@@ -71,23 +68,6 @@ public abstract class AbstractMyBatisInterceptor {
.toList()); .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);
}
/** /**
* 获取加密参数 * 获取加密参数
* *

View File

@@ -17,6 +17,7 @@
package top.continew.starter.security.crypto.core; package top.continew.starter.security.crypto.core;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import org.apache.ibatis.executor.resultset.ResultSetHandler; 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.Invocation;
import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.type.SimpleTypeRegistry; 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.annotation.FieldEncrypt;
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties; import top.continew.starter.security.crypto.utils.EncryptHelper;
import top.continew.starter.security.crypto.encryptor.IEncryptor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.sql.Statement; import java.sql.Statement;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 字段解密拦截器 * 字段解密拦截器
* *
* @author Charles7c * @author Charles7c
* @author lishuyan
* @since 1.4.0 * @since 1.4.0
*/ */
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})}) @Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implements Interceptor { 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 @Override
public Object intercept(Invocation invocation) throws Throwable { public Object intercept(Invocation invocation) throws Throwable {
// 获取执行结果
Object obj = invocation.proceed(); Object obj = invocation.proceed();
if (obj == null) { if (ObjectUtil.isNull(obj)) {
return null; return null;
} }
// 确保目标是 ResultSetHandler // 确保目标是 ResultSetHandler
@@ -68,6 +60,9 @@ public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implem
if (obj instanceof List<?> resultList) { if (obj instanceof List<?> resultList) {
// 处理列表结果 // 处理列表结果
this.decryptList(resultList); this.decryptList(resultList);
} else if (obj instanceof Map<?, ?> map) {
// 处理Map结果
this.decryptMap(map);
} else { } else {
// 处理单个对象结果 // 处理单个对象结果
this.decryptObject(obj); 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 结果对象 * @param result 结果对象
*/ */
private void decryptObject(Object result) { private void decryptObject(Object result) {
if (result == null) { if (ObjectUtil.isNull(result)) {
return; return;
} }
// String、Integer、Long 等简单类型对象无需处理 // String、Integer、Long 等简单类型对象无需处理
@@ -109,21 +116,16 @@ public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implem
} }
// 解密处理 // 解密处理
for (Field field : fieldList) { for (Field field : fieldList) {
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
Object fieldValue = ReflectUtil.getFieldValue(result, field); Object fieldValue = ReflectUtil.getFieldValue(result, field);
if (fieldValue == null) { if (fieldValue == null) {
continue; continue;
} }
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置 String strValue = String.valueOf(fieldValue);
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class).password(), properties if (CharSequenceUtil.isBlank(strValue)) {
.getPassword()); continue;
try {
String ciphertext = encryptor.decrypt(fieldValue.toString(), password, properties.getPrivateKey());
ReflectUtil.setFieldValue(result, field, ciphertext);
} catch (Exception e) {
// 解密失败时保留原值,避免影响正常业务流程
log.warn("解密失败,请检查加密配置", e);
} }
ReflectUtil.setFieldValue(result, field, EncryptHelper.decrypt(strValue, field
.getAnnotation(FieldEncrypt.class)));
} }
} }
} }

View File

@@ -28,12 +28,9 @@ import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds; 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.core.constant.StringConstants;
import top.continew.starter.security.crypto.annotation.FieldEncrypt; import top.continew.starter.security.crypto.annotation.FieldEncrypt;
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties; import top.continew.starter.security.crypto.utils.EncryptHelper;
import top.continew.starter.security.crypto.encryptor.IEncryptor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Arrays; import java.util.Arrays;
@@ -47,18 +44,13 @@ import java.util.regex.Pattern;
* 字段加密拦截器 * 字段加密拦截器
* *
* @author Charles7c * @author Charles7c
* @author lishuyan
* @since 1.4.0 * @since 1.4.0
*/ */
public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implements InnerInterceptor { public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implements InnerInterceptor {
private static final Logger log = LoggerFactory.getLogger(MyBatisEncryptInterceptor.class);
private static final Pattern PARAM_PAIRS_PATTERN = Pattern private static final Pattern PARAM_PAIRS_PATTERN = Pattern
.compile("#\\{ew\\.paramNameValuePairs\\.(" + Constants.WRAPPER_PARAM + "\\d+)\\}"); .compile("#\\{ew\\.paramNameValuePairs\\.(" + Constants.WRAPPER_PARAM + "\\d+)\\}");
private final CryptoProperties properties;
public MyBatisEncryptInterceptor(CryptoProperties properties) {
this.properties = properties;
}
@Override @Override
public void beforeQuery(Executor executor, public void beforeQuery(Executor executor,
@@ -124,22 +116,16 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
*/ */
private void encryptEntity(List<Field> fieldList, Object entity) { private void encryptEntity(List<Field> fieldList, Object entity) {
for (Field field : fieldList) { for (Field field : fieldList) {
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
Object fieldValue = ReflectUtil.getFieldValue(entity, field); Object fieldValue = ReflectUtil.getFieldValue(entity, field);
if (fieldValue == null) { if (fieldValue == null) {
continue; continue;
} }
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置 String strValue = String.valueOf(fieldValue);
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class).password(), properties if (CharSequenceUtil.isBlank(strValue)) {
.getPassword()); continue;
String ciphertext = fieldValue.toString();
try {
ciphertext = encryptor.encrypt(fieldValue.toString(), password, properties.getPublicKey());
} catch (Exception e) {
// 加密失败时保留原值,避免影响正常业务流程
log.warn("加密失败,请检查加密配置", e);
} }
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()) { if (matcher.matches()) {
String valueKey = matcher.group(1); String valueKey = matcher.group(1);
Object value = updateWrapper.getParamNameValuePairs().get(valueKey); Object value = updateWrapper.getParamNameValuePairs().get(valueKey);
Object ciphertext = this.doEncrypt(value, fieldEncrypt); updateWrapper.getParamNameValuePairs().put(valueKey, this.doEncrypt(value, fieldEncrypt));
updateWrapper.getParamNameValuePairs().put(valueKey, ciphertext);
} }
} }
} }
@@ -211,19 +196,14 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
* @param fieldEncrypt 字段加密注解 * @param fieldEncrypt 字段加密注解
*/ */
private Object doEncrypt(Object parameterValue, FieldEncrypt fieldEncrypt) { private Object doEncrypt(Object parameterValue, FieldEncrypt fieldEncrypt) {
if (parameterValue == null) { if (ObjectUtil.isNull(parameterValue)) {
return null; return null;
} }
IEncryptor encryptor = super.getEncryptor(fieldEncrypt); String strValue = String.valueOf(parameterValue);
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置 if (CharSequenceUtil.isBlank(strValue)) {
String password = ObjectUtil.defaultIfBlank(fieldEncrypt.password(), properties.getPassword()); return null;
try {
return encryptor.encrypt(parameterValue.toString(), password, properties.getPublicKey());
} catch (Exception e) {
// 加密失败时保留原值,避免影响正常业务流程
log.warn("加密失败,请检查加密配置", e);
} }
return parameterValue; return EncryptHelper.encrypt(strValue, fieldEncrypt);
} }
/** /**

View File

@@ -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) {
// 配置校验与配置注入
}
}

View File

@@ -29,26 +29,40 @@ import java.util.concurrent.ConcurrentHashMap;
* 对称加/解密处理器 * 对称加/解密处理器
* *
* @author Charles7c * @author Charles7c
* @author lishuyan
* @since 1.4.0 * @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<>(); 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; private final CryptoContext context;
}
return this.getCrypto(password).encryptHex(plaintext); public AbstractSymmetricCryptoEncryptor(CryptoContext context) {
super(context);
this.context = context;
} }
@Override @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)) { if (CharSequenceUtil.isBlank(ciphertext)) {
return ciphertext; return ciphertext;
} }
return this.getCrypto(password).decryptStr(ciphertext); return this.getCrypto(context.getPassword()).decryptStr(ciphertext);
} }
/** /**

View File

@@ -29,6 +29,10 @@ import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
*/ */
public class AesEncryptor extends AbstractSymmetricCryptoEncryptor { public class AesEncryptor extends AbstractSymmetricCryptoEncryptor {
public AesEncryptor(CryptoContext context) {
super(context);
}
@Override @Override
protected SymmetricAlgorithm getAlgorithm() { protected SymmetricAlgorithm getAlgorithm() {
return SymmetricAlgorithm.AES; return SymmetricAlgorithm.AES;

View File

@@ -30,12 +30,12 @@ import cn.hutool.core.codec.Base64;
public class Base64Encryptor implements IEncryptor { public class Base64Encryptor implements IEncryptor {
@Override @Override
public String encrypt(String plaintext, String password, String publicKey) throws Exception { public String encrypt(String plaintext) {
return Base64.encode(plaintext); return Base64.encode(plaintext);
} }
@Override @Override
public String decrypt(String ciphertext, String password, String privateKey) throws Exception { public String decrypt(String ciphertext) {
return Base64.decodeStr(ciphertext); return Base64.decodeStr(ciphertext);
} }
} }

View File

@@ -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);
}
}

View File

@@ -29,6 +29,10 @@ import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
*/ */
public class DesEncryptor extends AbstractSymmetricCryptoEncryptor { public class DesEncryptor extends AbstractSymmetricCryptoEncryptor {
public DesEncryptor(CryptoContext context) {
super(context);
}
@Override @Override
protected SymmetricAlgorithm getAlgorithm() { protected SymmetricAlgorithm getAlgorithm() {
return SymmetricAlgorithm.DES; return SymmetricAlgorithm.DES;

View File

@@ -20,6 +20,7 @@ package top.continew.starter.security.crypto.encryptor;
* 加/解密接口 * 加/解密接口
* *
* @author Charles7c * @author Charles7c
* @author lishuyan
* @since 1.4.0 * @since 1.4.0
*/ */
public interface IEncryptor { public interface IEncryptor {
@@ -28,21 +29,15 @@ public interface IEncryptor {
* 加密 * 加密
* *
* @param plaintext 明文 * @param plaintext 明文
* @param password 对称加密算法密钥
* @param publicKey 非对称加密算法公钥
* @return 加密后的文本 * @return 加密后的文本
* @throws Exception /
*/ */
String encrypt(String plaintext, String password, String publicKey) throws Exception; String encrypt(String plaintext);
/** /**
* 解密 * 解密
* *
* @param ciphertext 密文 * @param ciphertext 密文
* @param password 对称加密算法密钥
* @param privateKey 非对称加密算法私钥
* @return 解密后的文本 * @return 解密后的文本
* @throws Exception /
*/ */
String decrypt(String ciphertext, String password, String privateKey) throws Exception; String decrypt(String ciphertext);
} }

View File

@@ -29,6 +29,10 @@ import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
*/ */
public class PbeWithMd5AndDesEncryptor extends AbstractSymmetricCryptoEncryptor { public class PbeWithMd5AndDesEncryptor extends AbstractSymmetricCryptoEncryptor {
public PbeWithMd5AndDesEncryptor(CryptoContext context) {
super(context);
}
@Override @Override
protected SymmetricAlgorithm getAlgorithm() { protected SymmetricAlgorithm getAlgorithm() {
return SymmetricAlgorithm.PBEWithMD5AndDES; return SymmetricAlgorithm.PBEWithMD5AndDES;

View File

@@ -27,17 +27,29 @@ import cn.hutool.crypto.asymmetric.KeyType;
* </p> * </p>
* *
* @author Charles7c * @author Charles7c
* @author lishuyan
* @since 1.4.0 * @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 @Override
public String decrypt(String ciphertext, String password, String privateKey) throws Exception { public String encrypt(String plaintext) {
return new String(SecureUtil.rsa(privateKey, null).decrypt(Base64.decode(ciphertext), KeyType.PrivateKey)); 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));
} }
} }

View File

@@ -22,10 +22,16 @@ import top.continew.starter.security.crypto.encryptor.*;
* 加密/解密算法枚举 * 加密/解密算法枚举
* *
* @author Charles7c * @author Charles7c
* @author lishuyan
* @since 1.4.0 * @since 1.4.0
*/ */
public enum Algorithm { public enum Algorithm {
/**
* 默认使用配置属性的算法
*/
DEFAULT(null),
/** /**
* AES * AES
*/ */

View File

@@ -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;
}
}