感谢这两篇文章给予的参考帮助:

https://mp.weixin.qq.com/s/v1Uosv-81RNpsvXePRQTIA

https://blog.csdn.net/m0_37635053/article/details/127000204

一.前言

在开发中,我们经常会遇到需要对数据进行加密的场景。如果是少数字段,我们通常会单独对于这些字段进行加解密处理。但是对于很多字段都要进行加密的情况,前一种办法就无法灵活实现了。

这时候我们可以通过使用mybatis插件来实现数据的加解密。使用时只需要使用注解标记字段,该字段就可以被自动加密,同时在查询时还可以进行解密。需要注意:密码字段是不能够使用的,因为业务场景的不同,密码一般都是不可逆的,而且密码不需要查询。

有人会疑问,加密的数据如何进行模糊查询呢?很多人可能会将加密数据在内存中进行解密,然后进行比较,这种只适用于数据量很少的场景。这里有一篇文章:加密后的数据如何进行模糊查询?,大家可以参考。

二.思路

我实现的思路是上述文章中的分词加密,实现的原理很容易理解。加密后的数据肯定是无法直接和用户输入的数据进行比较的,而且因为是模糊查询的原因,用户输入的数据经过加密后也不一定能够匹配密文,这取决于分词的粒度。

而分词加密是将字符串进行分词后,将分词分别进行加密,然后将加密后的密文进行顺序拼接,将拼接后的字符串密文存储在扩展字段中。

三.代码实现

代码可以参考:https://blog.csdn.net/m0_37635053/article/details/127000204,我在这个代码的基础上进行了改进,可以实现加密字段模糊查询。

1.项目代码结构如下:

2.定义3个注解类


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
public @interface DecryptField {

}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
public @interface EncryptField {

    /**
     * 加密字段对应的关联字段
     *
     * @return
     */
    String extendField() default "";
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SensitiveData {
    
}

3.插件配置

package com.xhc.study.config;

import com.xhc.study.plugin.DecryptInterceptor;
import com.xhc.study.plugin.EncryptInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

@Configuration
public class MybatisConfig {
    @Resource
    private DecryptService decryptService;
    @Resource
    private EncryptService encryptService;
    @Bean
    public String myInterceptor(SqlSessionFactory sqlSessionFactory) {
        sqlSessionFactory.getConfiguration().addInterceptor(new EncryptInterceptor(encryptService));
        sqlSessionFactory.getConfiguration().addInterceptor(new DecryptInterceptor(decryptService));
        return "interceptor";
    }

}
package com.xhc.study.config;

public interface DecryptService {

    void decryptSensitiveField(Object targetObj) throws Exception;
}
package com.xhc.study.config;

import org.apache.ibatis.executor.parameter.ParameterHandler;

public interface EncryptService {

    void encryptSensitiveField(ParameterHandler parameterHandler) throws NoSuchFieldException, ClassNotFoundException;

}
package com.xhc.study.config.impl;

import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.xhc.study.config.DecryptService;
import com.xhc.study.entity.util.AESUtil;
import com.xhc.study.entity.util.SensitiveUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.util.*;

@Service
public class DecryptServiceImpl implements DecryptService {
    /**
     * 配置在yaml的密钥
     **/
    @Value("${aes.key}")
    private String key;

    @Override
    public void decryptSensitiveField(Object targetObj) throws Exception {
        if (null == targetObj) {
            return;
        }
        handleDecrypt(targetObj);
    }
    private void handleDecrypt(Object targetObj) throws Exception {
        if (Objects.isNull(targetObj)) {
            return;
        }
        String typeName = targetObj.getClass().getTypeName();
        if (typeName.startsWith("com.xhc")) {
            doDecryptField(targetObj);
            return;
        }
        handleDecryptCollection(targetObj);
    }
    /**
     * 处理集合
     *
     * @param result
     */
    private void handleDecryptCollection(Object result) throws Exception {
        if (result instanceof List) {
            handleDecryptList(result);
            return;
        }
        if (result instanceof Map) {
            handleDecryptMap(result);
            return;
        }
        if (result instanceof Set) {
            handleDecryptSet(result);
            return;
        }
    }
    private void handleDecryptSet(Object result) throws Exception {
        if (ObjectUtils.isNotEmpty(result)) {
            Set<Object> opSet = (HashSet) result;
            if (ObjectUtils.isEmpty(opSet)) {
                return;
            }
            for (Object item : opSet) {
                if (ObjectUtils.isEmpty(item)) {
                    continue;
                }
                handleDecrypt(item);
            }
        }
    }
    private void handleDecryptMap(Object result) throws Exception {
        if (ObjectUtils.isNotEmpty(result)) {
            Map<String, Object> map = (HashMap) result;
            if (ObjectUtils.isNotEmpty(map)) {
                for (Map.Entry<String, Object> key : map.entrySet()) {
                    Object mapObject = map.get(key.getKey());
                    if (ObjectUtils.isEmpty(mapObject)) {
                        continue;
                    }
                    handleDecrypt(mapObject);
                }
            }
        }
    }
    private void handleDecryptList(Object result) throws Exception {
        if (ObjectUtils.isNotEmpty(result)) {
            ArrayList<Object> list = (ArrayList) result;
            Class<?> aClass = list.get(0).getClass();
            if (!SensitiveUtil.isSensitiveClass(aClass)) {
                return;
            }
            List<Field> sensitiveField = SensitiveUtil.getSensitiveField(aClass);
            if (CollectionUtils.isNotEmpty(sensitiveField)) {
                for (Object obj : list) {
                    doDecryptField(obj, sensitiveField);
                }
            }
        }
    }
    private void doDecryptField(Object result) throws Exception {
        Class<?> aClass = result.getClass();
        if (!SensitiveUtil.isSensitiveClass(aClass)) {
            return;
        }
        List<Field> sensitiveFields = SensitiveUtil.getSensitiveField(aClass);
        if (CollectionUtils.isEmpty(sensitiveFields)) {
            return;
        }
        doDecryptField(result, sensitiveFields);
    }
    private void doDecryptField(Object result, List<Field> sensitiveFields) throws Exception {
        for (Field fs : sensitiveFields) {
            /**
             * 注:这里引用了hutool包 hutool-core
             **/
            Object fieldValue = ReflectUtil.getFieldValue(result, fs);
            if (fieldValue instanceof String) {
                if (ObjectUtils.isEmpty(fs)) {
                    continue;
                }
                String fieldValueStr = (String) fieldValue;
                if (fieldValueStr.startsWith("W86z:")) {
                    fieldValueStr = AESUtil.decrypt(fieldValueStr.substring("W86z:".length()), key);
                }
                ReflectUtil.setFieldValue(result, fs, fieldValueStr);
            } else {
                //递归
                handleDecrypt(fieldValue);
            }
        }
    }
}
package com.xhc.study.config.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.xhc.study.anno.EncryptField;
import com.xhc.study.config.EncryptService;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.xhc.study.entity.util.AESUtil;
import com.xhc.study.entity.util.EncryptUtil;
import com.xhc.study.entity.util.SensitiveUtil;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 加密
 */
@Service
public class EncryptServiceImpl implements EncryptService {

    @Value("${aes.key}")
    private String aesKey;

    @Autowired
    private EncryptUtil encryptUtil;

    @Override
    public void encryptSensitiveField(ParameterHandler parameterHandler) throws NoSuchFieldException, ClassNotFoundException {
        // 得到参数对象
        Object parameterObject = parameterHandler.getParameterObject();
        if (Objects.isNull(parameterObject)) {
            return;
        }
        if (parameterObject instanceof MapperMethod.ParamMap) {
            handleMethodInMapper(parameterHandler);
            return;
        }

        handleMethodNotInmapper(parameterObject);
    }

    private void handleMethodInMapper(ParameterHandler parameterHandler) throws NoSuchFieldException, ClassNotFoundException {
        MapperMethod.ParamMap<Object> paramMap = ( MapperMethod.ParamMap<Object>) parameterHandler.getParameterObject();
        for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
            String key = entry.getKey();
            Object paramValue = entry.getValue();
            if (!key.startsWith("param") && Objects.nonNull(paramValue)) {
                encrypt(parameterHandler, entry);
            }
        }
    }

    /**
     * 对DB字段进行加密
     *
     * @param parameterHandler
     * @param entry
     */
    private void encrypt(ParameterHandler parameterHandler, Map.Entry<String, Object> entry) throws NoSuchFieldException, ClassNotFoundException {
        Object paramValue = entry.getValue();
        //字符串
        if (paramValue instanceof String) {
            handleString(parameterHandler, entry);
            return;
        }
        //请求参数不是字符串
        handleMethodNotInmapper(paramValue);
    }
    /**
     * 对象 只处理:1.包含SensitiveData注解,属性包含EncryptField注解 2.属于com.atpingan的包或子包
     *
     * @param targetObj
     */
    private void handleMethodNotInmapper(Object targetObj) {
        if (Objects.isNull(targetObj)) {
            return;
        }
        Class<?> parameterClass = targetObj.getClass();
        Object entity = targetObj;
        Pair<Class<?>, Object> pair = handleQueryWrapper(targetObj);
        boolean isQueryWrapperOrLambdaQueryWrapper = Objects.nonNull(pair);
        if (isQueryWrapperOrLambdaQueryWrapper) {
            parameterClass = pair.getLeft();
            entity = pair.getRight();
        }
        if (Objects.isNull(entity)) {
            return;
        }
        //是否敏感类
        boolean sensitiveClass = SensitiveUtil.isSensitiveClass(parameterClass);
        if (!sensitiveClass) {
            return;
        }
        //路径前缀判断
        String typeName = parameterClass.getTypeName();
        if (!typeName.startsWith("com.xhc")) {
            return;
        }
        //获取敏感字段
        List<Field> sensitiveFields = SensitiveUtil.getSensitiveField(parameterClass);
        if (ObjectUtils.isEmpty(sensitiveFields)) {
            return;
        }
        encryptField(entity, sensitiveFields, parameterClass);
        if (isQueryWrapperOrLambdaQueryWrapper) {
            ReflectUtil.setFieldValue(targetObj, "entity", entity);
        }
    }
    private void encryptField(Object entity, List<Field> sensitiveFields, Class<?> clazz) {
        for (Field fs : sensitiveFields) {
            Object fieldValue = ReflectUtil.getFieldValue(entity, fs);
            if (fieldValue instanceof String) {
                String fieldValueStr = (String) fieldValue;
                ReflectUtil.setFieldValue(entity, fs, "W86z:"+AESUtil.encrypt(fieldValueStr, aesKey));

                // 获取该字段对应自定义注解里面的信息
                Annotation[] declaredAnnotations = fs.getDeclaredAnnotations();
                for (Annotation declaredAnnotation : declaredAnnotations) {
                    if (declaredAnnotation instanceof EncryptField){
                        EncryptField encryptField = (EncryptField) declaredAnnotation;
                        // 通过encryptField的值找到对应的属性进行设值
                        Field targetField = SensitiveUtil.getFieldByName(encryptField.extendField(), clazz);
                        if (targetField != null) {
                            // 将需要加密的值进行分词加密
                            String str = encryptUtil.splitEncryptStr(fieldValueStr);
                            ReflectUtil.setFieldValue(entity, targetField, str);
                        }
                    }
                }
            }
        }
    }
    private Pair<Class<?>, Object> handleQueryWrapper(Object targetObj) {
        if (targetObj instanceof LambdaQueryWrapper) {
            return Pair.of(((LambdaQueryWrapper<?>) targetObj).getEntityClass(), ((LambdaQueryWrapper<?>) targetObj).getEntity());
        }
        if (targetObj instanceof LambdaUpdateWrapper) {
            return Pair.of(((LambdaUpdateWrapper<?>) targetObj).getEntityClass(), ((LambdaUpdateWrapper<?>) targetObj).getEntity());
        }
        if (targetObj instanceof QueryWrapper) {
            return Pair.of(((QueryWrapper<?>) targetObj).getEntityClass(), ((QueryWrapper<?>) targetObj).getEntity());
        }
        return null;
    }
    private void handleString(ParameterHandler parameterHandler, Map.Entry<String, Object> entry) throws NoSuchFieldException, ClassNotFoundException {
        String key = entry.getKey();
        Object paramValue = entry.getValue();
        Class<? extends ParameterHandler> aClass = parameterHandler.getClass();
        Field mappedStatement = aClass.getDeclaredField("mappedStatement");
        ReflectUtil.setAccessible(mappedStatement);
        MappedStatement statement = (MappedStatement) ReflectUtil.getFieldValue(parameterHandler, mappedStatement);
        //方法命名空间
        String nameSpace = statement.getId();
        if (StringUtils.isBlank(nameSpace)) {
            return;
        }
        String methodName = nameSpace.substring(nameSpace.lastIndexOf(".") + 1);
        String className = nameSpace.substring(0, nameSpace.lastIndexOf("."));

        Method[] ms = Class.forName(className).getMethods();
        Optional<Method> optionalMethod = Arrays.stream(ms).filter(item -> StringUtils.equals(item.getName(), methodName)).findFirst();
        if (!optionalMethod.isPresent()) {
            return;
        }
        Method method = optionalMethod.get();
        ReflectUtil.setAccessible(method);
        //方法参数里面的请求参数注解列表
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        boolean sensitiveField = Arrays.stream(parameterAnnotations).anyMatch(item -> Boolean.TRUE.equals(Arrays.stream(item).anyMatch(ite -> {
            if (ite instanceof EncryptField) {
                EncryptField sensitive = (EncryptField) ite;
                // return StringUtils.equals(key, sensitive.value());
                return StringUtils.equals(key, sensitive.toString());
            }
            return false;
        })));
        if (!sensitiveField) {
            return;
        }
        String encrypt = "W86z:"+AESUtil.encrypt((String) paramValue, aesKey);
        entry.setValue(encrypt);
    }
}
package com.xhc.study.config;

import com.xhc.study.plugin.DecryptInterceptor;
import com.xhc.study.plugin.EncryptInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

@Configuration
public class MybatisConfig {
    @Resource
    private DecryptService decryptService;
    @Resource
    private EncryptService encryptService;
    @Bean
    public String myInterceptor(SqlSessionFactory sqlSessionFactory) {
        sqlSessionFactory.getConfiguration().addInterceptor(new EncryptInterceptor(encryptService));
        sqlSessionFactory.getConfiguration().addInterceptor(new DecryptInterceptor(decryptService));
        return "interceptor";
    }

}
package com.xhc.study.plugin;
import com.xhc.study.config.DecryptService;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.Properties;

@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class DecryptInterceptor implements Interceptor {

    private DecryptService decryptService;

    public DecryptInterceptor(DecryptService decryptService) {
        this.decryptService = decryptService;
    }
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = invocation.proceed();
        if (null != result) {
            decryptService.decryptSensitiveField(result);
        }
        return result;
    }
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {

    }
}
package com.xhc.study.plugin;

import com.xhc.study.config.EncryptService;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.*;

import java.sql.PreparedStatement;
import java.util.Properties;
/**
 * 查询解密
 * 依赖注解 SensitiveData 与 SensitiveField
 * 需要解密的类:类上需要标记SensitiveData 敏感属性 包括集合 需要添加SensitiveField
 * 解释: @Intercepts 注解开启拦截器,@Signature 注解定义拦截器的实际类型。
 *
 * @Signature中 type 属性指定当前拦截器使用StatementHandler 、ResultSetHandler、ParameterHandler,Executor的一种
 * method 属性指定使用以上四种类型的具体方法(可进入class内部查看其方法)。
 * args 属性指定预编译语句
 *
 * Interceptor 是Mybatis 的 拦截器接口
 */
@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})
})
public class EncryptInterceptor implements Interceptor {


    private EncryptService encryptService;

    public EncryptInterceptor(EncryptService encryptService) {
        this.encryptService = encryptService;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        encryptService.encryptSensitiveField(parameterHandler);
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        /**
         * 这里就是将拦截器和对象包装在一起:
         * ①获取拦截器的Intercepts注解的所有Signature参数,即该拦截器要拦截的类和对象的方法、参数
         * ②获取拦截对象的类
         * ③获取所有接口
         * ④根据返回的接口数量,判断是否要拦截的,要拦截的对象生成将拦截器和拦截对象封装在一起的代理对象
         */
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        /**
         * 在MyBatis配置文件中配置插件时可以设置参数,在setProperties函数中调用 Properties.getProperty("param1")
         * 方法可以得到配置的值
         */
    }
}

4.工具类

package com.xhc.study.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Base64Utils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

@Slf4j
public class AESUtil {
    private static final String KEY_ALGORITHM = "AES";
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";//默认的加密算法
    /**
     * 敏感数据自定义前缀
     */
    public static final String KEY_SENSITIVE = "W86z:";

    /**
     * AES 加密操作
     *
     * @param content 待加密内容
     * @return 返回Base64转码后的加密数据
     */
    public static String encrypt(String content, String key) {
        try {
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 创建密码器

            byte[] byteContent = content.getBytes(StandardCharsets.UTF_8);

            cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));// 初始化为加密模式的密码器

            byte[] result = cipher.doFinal(byteContent);// 加密

            //Base64是一种基于64个可打印字符来表示二进制数据的表示方法。
            return Base64Utils.encodeToString(result);//通过Base64转码返回
        } catch (Exception ex) {
            log.error(ex.getMessage());
        }
        return null;
    }
    /**
     * AES 解密操作
     *
     * @param content
     * @return
     */
    public static String decrypt(String content, String key) {
        try {
            //实例化
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);

            //使用密钥初始化,设置为解密模式
            cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key));

            //执行操作
            //Base64是一种基于64个可打印字符来表示二进制数据的表示方法。
            byte[] result = cipher.doFinal(Base64Utils.decodeFromString(content));

            return new String(result, StandardCharsets.UTF_8);
        } catch (Exception ex) {
            log.error(ex.getMessage());
        }
        return null;
    }
    /**
     * 生成加密秘钥
     * @return
     */
    private static Key getSecretKey(String key) throws NoSuchAlgorithmException {
        //返回生成指定算法密钥生成器的 KeyGenerator 对象
        KeyGenerator kg = null;
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        random.setSeed(key.getBytes());
        try {
            kg = KeyGenerator.getInstance(KEY_ALGORITHM);
            //AES 要求密钥长度为 128
            kg.init(128, random);
            //生成一个密钥
            SecretKey secretKey = kg.generateKey();
            return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);// 转换为AES专用密钥
        } catch (NoSuchAlgorithmException ex) {
            log.error(ex.getMessage());
        }
        return null;
    }

    public static String encryptFieldStr(String splitWord, String key) {
        String[] str = splitWord.split("|");

        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < str.length; i++) {
            stringBuffer.append(encrypt(str[i], key));
        }
        return stringBuffer.toString();
    }
}
package com.xhc.study.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.springframework.util.Assert;
import org.wltea.analyzer.lucene.IKAnalyzer;

import java.io.StringReader;

@Slf4j
public class AnalyzerUtil {

    public static String splitWord(String word) {
        Assert.hasText(word, "需要进行拆分的字符串不能为空");
        String resultStr = "";

        Analyzer analyzer = new IKAnalyzer(true);
        StringReader reader = new StringReader(word);
        try {
            TokenStream ts = analyzer.tokenStream("", reader);
            CharTermAttribute term = ts.getAttribute(CharTermAttribute.class);

            while (ts.incrementToken()) {
                resultStr = resultStr + term.toString() + "|";
            }
        } catch (Exception e) {
            log.error("分词出现错误:", e);
        } finally {
            reader.close();
            return resultStr;
        }
    }
}
package com.xhc.study.util;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

@Component
public class EncryptUtil {

    @Value("${aes.key}")
    private String aesKey;

    /**
     * 对分割后的字符串进行加密
     *
     * @param str
     * @return
     */
    public String splitEncryptStr(String str) {
        Assert.hasText(str, "需要加密的字符串不能为空");

        // 将字符串进行分词
        String splitWord = AnalyzerUtil.splitWord(str);

        // 分别将分词后的信息进行加密,然后拼接
        String extendField = AESUtil.encryptFieldStr(splitWord, aesKey);

        // 处理加密的扩展字段
        return extendField;
    }

}
package com.xhc.study.util;

import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.xhc.study.anno.EncryptField;
import com.xhc.study.anno.SensitiveData;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class SensitiveUtil {
    /**
     * 判断一个类是否敏感类,即是否有注解 @SensitiveData
     * @param clazz
     * @return
     */
    public static boolean isSensitiveClass(Class<?> clazz){
        SensitiveData sensitiveData = AnnotationUtils.findAnnotation(clazz,SensitiveData.class);
        return ObjectUtils.isNotEmpty(sensitiveData);
    }
    /**
     * 获取敏感类里面的敏感字段
     * @param clazz
     * @return
     */
    public static List<Field> getSensitiveField(Class<?> clazz){
        List<Field> list = new ArrayList<>();
        Field[] fields = ReflectUtil.getFields(clazz);
        for (Field fs: fields) {
            boolean annotationPresent = fs.isAnnotationPresent(EncryptField.class);
            if(annotationPresent){
                list.add(fs);
            }
        }
        return list;
    }

    public static Field getFieldByName(String fieldName, Class<?> clazz){
        Field[] fields = ReflectUtil.getFields(clazz);
        List<Field> fieldList = Arrays.stream(fields).filter(field -> fieldName.equals(field.getName())).collect(Collectors.toList());
        return CollectionUtils.isEmpty(fieldList) ? null : fieldList.get(0);
    }

}

5.测试的实体对象

package com.xhc.study.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.xhc.study.anno.EncryptField;
import com.xhc.study.anno.SensitiveData;
import lombok.Data;

import java.io.Serializable;

@Data
@TableName("`order`")
@SensitiveData
public class Order implements Serializable {

    /**
     * 主键id
     */
    @TableId(type = IdType.INPUT)
    private Long id;

    /**
     * 用户姓名
     */
    private String userName;

    /**
     * 用户手机号
     */
    private String phone;

    /**
     * 用户收获地址
     */
    @EncryptField(extendField = "addressExtendField")
    private String address;

    /**
     * 扩展字段,该扩展字段用来存储被加密的信息经过分词后加密的结果
     */
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private String addressExtendField;

}

5.需要注意:

// 需要对加密关联字段进行默认空字符填充,因为在执行insert时,首先会预编译sql,然后才会通过拦截器设置加密参数,如果预编译的sql中没有指定属性,加密参数也设置不上 order.setAddressExtendField("");

orderDao.insert(order);

四:结语

按照上述的流程即可完成基于mybatis插件实现加解密模糊查询的功能,以后使用只需要在需要的字段上使用注解即可。有什么疑问,可以在下方留言。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐