笔记:Java中各项目集成Mybatis-Plus字段加密-解密
通过这段时间的任务需求完成后,我发现字段加密这个功能有必要记录一下,也和广大网友分享一下,防止以后踩坑。说起字段加密,大家都不陌生,最为关注的也就是国密算法吧。其中Mybatis-Plus都有定义,但是不开源,我建议大家使用Mybatis-Plus自带的,毕竟不花钱只是暂时适合!接下来我们围绕国密算法SM2、SM3、SM4进行讲解。
提示:以下是本篇文章正文内容,下面案例可供参考
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.7.21version>
dependency>
<dependency>
<groupId>org.bouncycastlegroupId>
<artifactId>bcprov-jdk15onartifactId>
<version>1.59version>
dependency>
<dependency>
<groupId>org.jasyptgroupId>
<artifactId>jasyptartifactId>
<version>1.9.3version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.1version>
dependency>
代码如下(示例):
public enum Algorithm {
SM2,
SM3,
SM4;
private Algorithm() {
}
}
代码如下(示例):
import java.lang.annotation.*;
/**
* @author dpy
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface FieldEncrypt {
/**
* 默认 SM2
*/
Algorithm algorithm() default Algorithm.SM2;
/*Class extends IEncryptor> encryptor() default IEncryptor.class;*/
}
代码如下(示例):
**
* @author dpy
*/
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class MybatisEncryptionPlugin implements Interceptor {
private static final Logger log = LoggerFactory.getLogger(MybatisEncryptionPlugin.class);
/**
* mybatis拦截器
*
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
// args[0] 为 MappedStatement 数据
MappedStatement ms = (MappedStatement) args[0];
// 获取 sqlCommandType ---------> (提交类型: insert/update )
SqlCommandType sqlCommandType = ms.getSqlCommandType();
// 获取对象数据 ---------------> args[1]
Object parameter = args[1];
if (Util.encryptionRequired(parameter, sqlCommandType)) {
// 判断参数是map还是String
if (parameter instanceof MapperMethod.ParamMap) {
//noinspection unchecked
MapperMethod.ParamMap<Object> paramMap = (MapperMethod.ParamMap<Object>) parameter;
encryptParamMap(paramMap);
} else {
encryptEntity(parameter);
}
}
return invocation.proceed();
}
private void encryptEntity(Object parameter) throws MybatisCryptoException {
processFields(EncryptorProvider.get(parameter.getClass()), parameter);
}
private void encryptParamMap(MapperMethod.ParamMap<Object> paramMap) throws MybatisCryptoException {
Set<Map.Entry<String, Object>> entrySet = paramMap.entrySet();
for (Map.Entry<String, Object> entry : entrySet) {
String key = entry.getKey();
Object value = entry.getValue();
if (value == null || key == null) {
continue;
}
if (value instanceof ArrayList) {
//noinspection rawtypes
ArrayList list = (ArrayList) value;
if (!list.isEmpty()) {
Object firstItem = list.get(0);
Class<?> itemClass = firstItem.getClass();
Set<Field> encryptedFields = EncryptorProvider.get(itemClass);
for (Object item : list) {
processFields(encryptedFields, item);
}
}
} else {
processFields(EncryptorProvider.get(value.getClass()), value);
}
}
}
private void processFields(Set<Field> encryptedFields, Object entry) throws MybatisCryptoException {
if (encryptedFields == null || encryptedFields.isEmpty()) {
return;
}
for (Field field : encryptedFields) {
FieldEncrypt fieldEncrypt = field.getAnnotation(FieldEncrypt.class);
if (fieldEncrypt == null) {
continue;
}
try {
field.setAccessible(true);
Object originalVal = field.get(entry);
if (originalVal == null) {
continue;
}
// 获取field的注解类型
Algorithm algorithm = fieldEncrypt.algorithm();
// 调用加密方法
String encryptedVal = DefaultEncryptor.encrypt(algorithm, originalVal.toString());
field.set(entry, encryptedVal);
} catch (Exception e) {
throw new MybatisCryptoException(e);
}
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
代码如下(示例):
/**
* @author dpy
*/
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class MybatisDecryptionPlugin implements Interceptor {
private static final Logger log = LoggerFactory.getLogger(MybatisDecryptionPlugin.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object result = invocation.proceed();
if (result == null) {
return null;
}
if (result instanceof ArrayList) {
//noinspection rawtypes
ArrayList resultList = (ArrayList) result;
if (resultList.isEmpty()) {
return result;
}
Object firstItem = resultList.get(0);
boolean needToDecrypt = Util.decryptionRequired(firstItem);
if (!needToDecrypt) {
return result;
}
Set<Field> encryptedFields = EncryptorProvider.get(firstItem.getClass());
if (encryptedFields == null || encryptedFields.isEmpty()) {
return result;
}
for (Object item : resultList) {
decryptEntity(encryptedFields, item);
}
} else {
if (Util.decryptionRequired(result)) {
decryptEntity(EncryptorProvider.get(result.getClass()), result);
}
}
return result;
}
private void decryptEntity(Set<Field> encryptedFields, Object item) throws MybatisCryptoException {
if (encryptedFields == null || encryptedFields.isEmpty()) {
return;
}
for (Field field : encryptedFields) {
FieldEncrypt fieldEncrypt = field.getAnnotation(FieldEncrypt.class);
if (fieldEncrypt != null) {
try {
field.setAccessible(true);
Object originalVal = field.get(item);
if (originalVal != null) {
Algorithm algorithm = fieldEncrypt.algorithm();
String decryptedVal = DefaultEncryptor.decrypt(algorithm, originalVal.toString());
field.set(item, decryptedVal);
}
} catch (Exception e) {
throw new MybatisCryptoException(e);
}
}
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
/**
* @author dpy
*/
public class Util {
public static boolean encryptionRequired(Object parameter, SqlCommandType sqlCommandType) {
return (sqlCommandType == SqlCommandType.INSERT || sqlCommandType == SqlCommandType.UPDATE
||sqlCommandType == SqlCommandType.SELECT) && decryptionRequired(parameter);
}
public static boolean decryptionRequired(Object parameter) {
return !(parameter == null || parameter instanceof Double || parameter instanceof Integer
|| parameter instanceof Long || parameter instanceof Short || parameter instanceof Float
|| parameter instanceof Boolean || parameter instanceof Character
|| parameter instanceof Byte);
}
}
/**
* @author dpy
*/
public class EncryptorProvider {
private static final Map<Class<?>, Set<Field>> encryptedFieldCache = new ConcurrentHashMap<>();
public static Set<Field> get(Class<?> parameterClass) {
return encryptedFieldCache.computeIfAbsent(parameterClass, aClass -> {
Field[] declaredFields = aClass.getDeclaredFields();
return Arrays.stream(declaredFields).filter(field ->
field.isAnnotationPresent(FieldEncrypt.class) && field.getType() == String.class)
.collect(Collectors.toSet());
});
}
}
拦截器写完要想产生作用,就得声明拦截器。注:如果以后封装加密和解密模块的话,需要利用SpringBoot中META-INF/spring.factories自动装配。
代码如下(示例):
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author dpy
*/
@Configuration(proxyBeanMethods = false)
public class MyBatisCryptoAutoConfiguration {
@Bean
@ConditionalOnMissingBean(MybatisEncryptionPlugin.class)
public MybatisEncryptionPlugin encryptionInterceptor() {
return new MybatisEncryptionPlugin();
}
@Bean
@ConditionalOnMissingBean(MybatisDecryptionPlugin.class)
public MybatisDecryptionPlugin decryptionInterceptor() {
return new MybatisDecryptionPlugin();
}
}
我这里异常处理是直接拿MyBatis-Plus中定义的,在此声明一下,希望各位大佬自己定义哈,我这里只是参考。
代码如下(示例):
public class MybatisCryptoException extends Exception {
public MybatisCryptoException() {
}
public MybatisCryptoException(String message) {
super(message);
}
public MybatisCryptoException(String message, Throwable cause) {
super(message, cause);
}
public MybatisCryptoException(Throwable cause) {
super(cause);
}
public MybatisCryptoException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
我自己项目中是采用公司写好的 Util 类,不方便展示,不好意思哈!接下来咱们采用Hutool中定义的国密算法简单应用,其中有些许问题,请各位大佬和众多网友帮忙。
代码如下(示例):
/**
* @author dpy
*/
@Configuration
public class DefaultEncryptor{
public static String encrypt(Algorithm var1, String var2) throws Exception {
return commonCrypt(var1, var2, true);
}
public static String decrypt(Algorithm var1, String var2) throws Exception {
return commonCrypt(var1, var2, false);
}
/**
* 加减密算法
*
* @param var1 算法类型
* @param var2
* @param state true 加密; false 解密
* @return
*/
public static String commonCrypt(Algorithm var1, String var2, boolean state) throws IOException {
if (var1 == Algorithm.SM2) {
// 自定义秘钥
KeyPair pair = SecureUtil.generateKeyPair("SM2");
byte[] privateKey = pair.getPrivate().getEncoded();
byte[] publicKey = pair.getPublic().getEncoded();
SM2 sm2 = SmUtil.sm2(privateKey, publicKey);
// 公钥加密,私钥解密
String encryptStr = sm2.encryptBcd(var2, KeyType.PublicKey);
/**
* 直接加密后,得到加密的值传入解密中可以正常使用(同步执行)
* String encryptStr = sm2.encryptBcd(text, KeyType.PublicKey);
* String decryptStr = StrUtil.utf8Str(sm2.decryptFromBcd(encryptStr, KeyType.PrivateKey));
*
* 但是传入相同加密值var2,直接三目法走 StrUtil.utf8Str(sm2.decryptFromBcd(var2, KeyType.PrivateKey))报错
* 我测试好多,原因至今不明,所以用了项目的Util类,可以用三目直接返回
* 这个问题SM2和SM4都是存在的,也许hutool这个我不会用,不能直接传参吧
* 如果有哪位大佬看到后请多指教,我百度找不到答案,很希望得到答案。
*
* 本示例仅供参考如何构建,至于算法实现可以详细阅读hutool官网
* https://www.hutool.cn/docs/
*
*
*/
return state ? encryptStr : StrUtil.utf8Str(sm2.decryptFromBcd(var2, KeyType.PrivateKey));
} else if (var1 == Algorithm.SM3) {
return state ? SmUtil.sm3(var2) : var2;
} else {
// 国密算法SM4
SymmetricCrypto sm4 = SmUtil.sm4();
String encryptHex = sm4.encryptHex(var2);
return state ? encryptHex : sm4.decryptStr(var2, CharsetUtil.CHARSET_UTF_8);
}
}
}
以上就是今天要记录与大家分享的内容,本站也是我第一次写文章,写的不好,希望各位大佬不要喷,阅读中如有问题或者我上述问题有解决方案请与我沟通一下,谢谢哈,非常感谢大家的阅读!
注:本文是结合Mybatis-plus集成的加减密和Hutool国密算法共同完成的,仅仅只能参考加密和解密的封装过程与构建,不能作为真实项目中运算运行,如果可以解决上面Hutool问题就可以用哈,再次感谢大家。