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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
}
/**
* 获取加密参数
*

View File

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

View File

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

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 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);
}
/**

View File

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

View File

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

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 DesEncryptor(CryptoContext context) {
super(context);
}
@Override
protected SymmetricAlgorithm getAlgorithm() {
return SymmetricAlgorithm.DES;

View File

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

View File

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

View File

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

View File

@@ -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
*/

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