Java中各项目集成Mybatis-Plus字段加密-解密

笔记:Java中各项目集成Mybatis-Plus字段加密-解密

引言

通过这段时间的任务需求完成后,我发现字段加密这个功能有必要记录一下,也和广大网友分享一下,防止以后踩坑。说起字段加密,大家都不陌生,最为关注的也就是国密算法吧。其中Mybatis-Plus都有定义,但是不开源,我建议大家使用Mybatis-Plus自带的,毕竟不花钱只是暂时适合!接下来我们围绕国密算法SM2、SM3、SM4进行讲解。


提示:以下是本篇文章正文内容,下面案例可供参考

一、Pom.xml文件引用包

		<!--这里使用 hutool 版本跟随自己项目就好-->
		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.7.21</version>
		</dependency>
		<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcprov-jdk15on</artifactId>
			<version>1.59</version>
		</dependency>

		<dependency>
			<groupId>org.jasypt</groupId>
			<artifactId>jasypt</artifactId>
			<version>1.9.3</version>
		</dependency>

		<!--mybatis-plus使用 版本跟随自己项目-->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.5.1</version>
		</dependency>

二、自定义枚举和注解

1.枚举 Algorithm

代码如下(示例):

public enum Algorithm {

	SM2,
	SM3,
	SM4;

	private Algorithm() {
	}
}

2.注解 FieldEncrypt

代码如下(示例):

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

三、定义拦截器

在这里插入图片描述

1.加密拦截器(MybatisEncryptionPlugin)

代码如下(示例):

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

2.解密拦截器(MybatisDecryptionPlugin)

代码如下(示例):

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

	}

3.拦截器引用类

3.1 Util

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

3.2 EncryptorProvider

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

四、声明拦截器(MyBatisCryptoAutoConfiguration)

拦截器写完要想产生作用,就得声明拦截器。注:如果以后封装加密和解密模块的话,需要利用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问题就可以用哈,再次感谢大家。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值