笔记:Java中各项目集成Mybatis-Plus字段加密-解密
引言
通过这段时间的任务需求完成后,我发现字段加密这个功能有必要记录一下,也和广大网友分享一下,防止以后踩坑。说起字段加密,大家都不陌生,最为关注的也就是国密算法吧。其中Mybatis-Plus都有定义,但是不开源,我建议大家使用Mybatis-Plus自带的,毕竟不花钱只是暂时适合!接下来我们围绕国密算法SM2、SM3、SM4进行讲解。
提示:以下是本篇文章正文内容,下面案例可供参考
一、Pom.xml文件引用包
<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>
<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.*;
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface FieldEncrypt {
Algorithm algorithm() default Algorithm.SM2;
}
三、定义拦截器
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);
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
SqlCommandType sqlCommandType = ms.getSqlCommandType();
Object parameter = args[1];
if (Util.encryptionRequired(parameter, sqlCommandType)) {
if (parameter instanceof MapperMethod.ParamMap) {
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) {
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;
}
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)
代码如下(示例):
@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) {
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
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
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;
@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中定义的国密算法简单应用,其中有些许问题,请各位大佬和众多网友帮忙。
代码如下(示例):
@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);
}
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);
return state ? encryptStr : StrUtil.utf8Str(sm2.decryptFromBcd(var2, KeyType.PrivateKey));
} else if (var1 == Algorithm.SM3) {
return state ? SmUtil.sm3(var2) : var2;
} else {
SymmetricCrypto sm4 = SmUtil.sm4();
String encryptHex = sm4.encryptHex(var2);
return state ? encryptHex : sm4.decryptStr(var2, CharsetUtil.CHARSET_UTF_8);
}
}
}
总结
以上就是今天要记录与大家分享的内容,本站也是我第一次写文章,写的不好,希望各位大佬不要喷,阅读中如有问题或者我上述问题有解决方案请与我沟通一下,谢谢哈,非常感谢大家的阅读! 注:本文是结合Mybatis-plus集成的加减密和Hutool国密算法共同完成的,仅仅只能参考加密和解密的封装过程与构建,不能作为真实项目中运算运行,如果可以解决上面Hutool问题就可以用哈,再次感谢大家。
|