From 5d10a28aa1c4ade0a51235e302c46143b90f7bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=A6=E4=B8=AD=E8=87=AA=E6=9C=89=E9=A2=9C=E5=A6=82?= =?UTF-8?q?=E7=8E=89?= <1206770390@qq.com> Date: Mon, 21 Jul 2025 10:14:25 +0000 Subject: [PATCH] =?UTF-8?q?refactor(security/crypto)=EF=BC=9A=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E5=8A=A0/=E8=A7=A3=E5=AF=86=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E9=80=BB=E8=BE=91=EF=BC=8C=E5=B0=81=E8=A3=85?= =?UTF-8?q?=20EncryptHelper=20=E5=B7=A5=E5=85=B7=E7=B1=BB=EF=BC=8C?= =?UTF-8?q?=E6=8F=90=E4=BE=9B=E7=BB=9F=E4=B8=80=E7=9A=84=E5=8A=A0/?= =?UTF-8?q?=E8=A7=A3=E5=AF=86=E6=96=B9=E6=B3=95=EF=BC=8C=E6=96=B9=E4=BE=BF?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=80=85=E7=81=B5=E6=B4=BB=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=8A=A0/=E8=A7=A3=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../starter/core/util/TreeBuildUtils.java | 44 +++- .../crud/service/CrudServiceImpl.java | 4 +- .../crypto/annotation/FieldEncrypt.java | 13 +- .../CryptoAutoConfiguration.java | 7 +- .../autoconfigure/CryptoProperties.java | 15 ++ .../core/AbstractMyBatisInterceptor.java | 20 -- .../core/MyBatisDecryptInterceptor.java | 54 ++--- .../core/MyBatisEncryptInterceptor.java | 46 ++-- .../crypto/encryptor/AbstractEncryptor.java | 30 +++ .../AbstractSymmetricCryptoEncryptor.java | 32 ++- .../crypto/encryptor/AesEncryptor.java | 4 + .../crypto/encryptor/Base64Encryptor.java | 4 +- .../crypto/encryptor/CryptoContext.java | 99 ++++++++ .../crypto/encryptor/DesEncryptor.java | 4 + .../security/crypto/encryptor/IEncryptor.java | 11 +- .../encryptor/PbeWithMd5AndDesEncryptor.java | 4 + .../crypto/encryptor/RsaEncryptor.java | 24 +- .../security/crypto/enums/Algorithm.java | 6 + .../security/crypto/utils/EncryptHelper.java | 211 ++++++++++++++++++ 19 files changed, 513 insertions(+), 119 deletions(-) create mode 100644 continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/AbstractEncryptor.java create mode 100644 continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/CryptoContext.java create mode 100644 continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/utils/EncryptHelper.java diff --git a/continew-starter-core/src/main/java/top/continew/starter/core/util/TreeBuildUtils.java b/continew-starter-core/src/main/java/top/continew/starter/core/util/TreeBuildUtils.java index 67a10a46..5b285b05 100644 --- a/continew-starter-core/src/main/java/top/continew/starter/core/util/TreeBuildUtils.java +++ b/continew-starter-core/src/main/java/top/continew/starter/core/util/TreeBuildUtils.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 List> build(List list, K parentId, TreeNodeConfig treeNodeConfig, NodeParser nodeParser) { + public static List> build(List list, + K parentId, + TreeNodeConfig treeNodeConfig, + NodeParser nodeParser) { if (CollUtil.isEmpty(list)) { return CollUtil.newArrayList(); } @@ -88,7 +107,10 @@ public class TreeBuildUtils extends TreeUtil { * @param 节点 ID 类型(如 Long、String) * @return 构建完成的树形结构(可能包含多个顶级根节点) */ - public static List> buildMultiRoot(List list, Function getId, Function getParentId, NodeParser parser) { + public static List> buildMultiRoot(List list, + Function getId, + Function getParentId, + NodeParser parser) { if (CollUtil.isEmpty(list)) { return CollUtil.newArrayList(); } @@ -112,8 +134,11 @@ public class TreeBuildUtils extends TreeUtil { * @param parser 树节点属性映射器,用于将原始节点 T 转为 Tree 节点 * @return 构建完成的树形结构(可能包含多个顶级根节点) */ - public static List> buildMultiRoot(List list, Function getId, Function getParentId, - TreeNodeConfig treeNodeConfig, NodeParser parser) { + public static List> buildMultiRoot(List list, + Function getId, + Function getParentId, + TreeNodeConfig treeNodeConfig, + NodeParser 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); } } diff --git a/continew-starter-extension/continew-starter-extension-crud/continew-starter-extension-crud-mp/src/main/java/top/continew/starter/extension/crud/service/CrudServiceImpl.java b/continew-starter-extension/continew-starter-extension-crud/continew-starter-extension-crud-mp/src/main/java/top/continew/starter/extension/crud/service/CrudServiceImpl.java index ae2309cd..d71c132e 100644 --- a/continew-starter-extension/continew-starter-extension-crud/continew-starter-extension-crud-mp/src/main/java/top/continew/starter/extension/crud/service/CrudServiceImpl.java +++ b/continew-starter-extension/continew-starter-extension-crud/continew-starter-extension-crud-mp/src/main/java/top/continew/starter/extension/crud/service/CrudServiceImpl.java @@ -105,7 +105,9 @@ public abstract class CrudServiceImpl, 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 getId = createMethodReference(listClass, valueGetter); diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/annotation/FieldEncrypt.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/annotation/FieldEncrypt.java index dccb566c..93c297bb 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/annotation/FieldEncrypt.java +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/annotation/FieldEncrypt.java @@ -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 ""; } \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/autoconfigure/CryptoAutoConfiguration.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/autoconfigure/CryptoAutoConfiguration.java index 82d3d6f0..c8b86f0e 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/autoconfigure/CryptoAutoConfiguration.java +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/autoconfigure/CryptoAutoConfiguration.java @@ -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."); } } diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/autoconfigure/CryptoProperties.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/autoconfigure/CryptoProperties.java index 1b071d31..19b6f3f1 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/autoconfigure/CryptoProperties.java +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/autoconfigure/CryptoProperties.java @@ -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; } diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/core/AbstractMyBatisInterceptor.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/core/AbstractMyBatisInterceptor.java index a5603853..63a7d548 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/core/AbstractMyBatisInterceptor.java +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/core/AbstractMyBatisInterceptor.java @@ -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 encryptorClass = fieldEncrypt.encryptor(); - // 使用预定义加/解密处理器 - if (encryptorClass == IEncryptor.class) { - Algorithm algorithm = fieldEncrypt.value(); - return ReflectUtil.newInstance(algorithm.getEncryptor()); - } - // 使用自定义加/解密处理器 - return SpringUtil.getBean(encryptorClass); - } - /** * 获取加密参数 * diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/core/MyBatisDecryptInterceptor.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/core/MyBatisDecryptInterceptor.java index fc993c09..6b5802d7 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/core/MyBatisDecryptInterceptor.java +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/core/MyBatisDecryptInterceptor.java @@ -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))); } } } \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/core/MyBatisEncryptInterceptor.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/core/MyBatisEncryptInterceptor.java index da19b39e..261032f4 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/core/MyBatisEncryptInterceptor.java +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/core/MyBatisEncryptInterceptor.java @@ -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 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); } /** diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/AbstractEncryptor.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/AbstractEncryptor.java new file mode 100644 index 00000000..a2d62cb7 --- /dev/null +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/AbstractEncryptor.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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) { + // 配置校验与配置注入 + } + +} diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/AbstractSymmetricCryptoEncryptor.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/AbstractSymmetricCryptoEncryptor.java index 9591e46a..a43f9eb0 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/AbstractSymmetricCryptoEncryptor.java +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/AbstractSymmetricCryptoEncryptor.java @@ -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 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); } /** diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/AesEncryptor.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/AesEncryptor.java index 6f5633e3..cc686a4c 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/AesEncryptor.java +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/AesEncryptor.java @@ -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; diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/Base64Encryptor.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/Base64Encryptor.java index c6748589..8e08dcfa 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/Base64Encryptor.java +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/Base64Encryptor.java @@ -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); } } diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/CryptoContext.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/CryptoContext.java new file mode 100644 index 00000000..9a131344 --- /dev/null +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/CryptoContext.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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); + } +} diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/DesEncryptor.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/DesEncryptor.java index 790595b6..b4ec1733 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/DesEncryptor.java +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/DesEncryptor.java @@ -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; diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/IEncryptor.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/IEncryptor.java index 3d2c3b55..1ff83cf6 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/IEncryptor.java +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/IEncryptor.java @@ -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); } diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/PbeWithMd5AndDesEncryptor.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/PbeWithMd5AndDesEncryptor.java index 84ba72a5..476ea56e 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/PbeWithMd5AndDesEncryptor.java +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/PbeWithMd5AndDesEncryptor.java @@ -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; diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/RsaEncryptor.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/RsaEncryptor.java index 7ca34bae..160085a7 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/RsaEncryptor.java +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/encryptor/RsaEncryptor.java @@ -27,17 +27,29 @@ import cn.hutool.crypto.asymmetric.KeyType; *

* * @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)); } } diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/enums/Algorithm.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/enums/Algorithm.java index ab20db04..a0840d03 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/enums/Algorithm.java +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/enums/Algorithm.java @@ -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 */ diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/utils/EncryptHelper.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/utils/EncryptHelper.java new file mode 100644 index 00000000..a578b66e --- /dev/null +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/utils/EncryptHelper.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 ENCRYPTOR_CACHE = new ConcurrentHashMap<>(); + + /** + * 初始化默认配置 + * + * @param properties 加密配置 + */ + public static void init(CryptoProperties properties) { + defaultProperties = properties; + } + + /** + * 注册加密执行者到缓存 + *

+ * 计算 CryptoContext 对象的hashCode作为缓存中的key,通过hashCode查询缓存中存在则直接返回,不存在则创建并缓存 + *

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