mybatis使用interceptor实现字段加解密

mybatis使用interceptor实现字段加解密

1.简介

本文将介绍使用springboot+mybatis拦截器+自定义注解的形式对敏感数据进行存储前拦截加密的。对手机号进行加密数据库存储,查询时解密。
Mybatis plugin: MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis
允许使用插件来拦截的方法调用包括:
//①语句执行拦截
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
// ②参数获取、设置时进行拦截
ParameterHandler (getParameterObject, setParameters)
// ③对返回结果进行拦截
ResultSetHandler (handleResultSets, handleOutputParameters)
//④sql语句拦截
StatementHandler (prepare, parameterize, batch, update, query)

1.1 原理图

mybatis使用interceptor实现字段加解密_第1张图片

1.2 demo结构图

mybatis使用interceptor实现字段加解密_第2张图片

1.3 mysql准备
CREATE TABLE `book` (
  `userid` int(64) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) DEFAULT NULL,
  `ustatus` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`userid`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8;

代码部分

2.注册拦截器(作用是把插件注册到拦截器中)
@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";
    }
}
3.写3个注解(只要注解打标)

这里是3个注解,为了节约空间才写到这里一堆

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

}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
public @interface EncryptField {
    String value() default "";
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SensitiveData {
}
4.写加密拦截器
/**
 * 查询解密
 * 依赖注解 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")
         * 方法可以得到配置的值
         */
    }
}
5.解密拦截器
@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) {

    }
}
6.实现类接口

这里是两个接口,为了节约空间才写到一起的。

public interface DecryptService {
    void decryptSensitiveField(Object targetObj) throws Exception;
}

public interface EncryptService {
    void encryptSensitiveField(ParameterHandler parameterHandler) throws NoSuchFieldException, ClassNotFoundException;
}
7.解密实现类
@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.atpingan")) {
            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);
            }
        }
    }
}
8.加密实现类
@Service
public class EncryptServiceImpl implements EncryptService {

    @Value("${aes.key}")
    private String aesKey;
    @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.atping")) {
            return;
        }
        //获取敏感字段
        List<Field> sensitiveFields = SensitiveUtil.getSensitiveField(parameterClass);
        if (ObjectUtils.isEmpty(sensitiveFields)) {
            return;
        }
        encryptField(entity, sensitiveFields);
        if (isQueryWrapperOrLambdaQueryWrapper) {
            ReflectUtil.setFieldValue(targetObj, "entity", entity);
        }
    }
    private void encryptField(Object entity, List<Field> sensitiveFields) {
        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));
            }

        }
    }
    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 false;
        })));
        if (!sensitiveField) {
            return;
        }
        String encrypt = "W86z:"+AESUtil.encrypt((String) paramValue, aesKey);
        entry.setValue(encrypt);
    }
}
9.加解密工具类
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;
    }
}
10. AES加密工具类(可可根据自己喜好选择加解密方法)
@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;
    }
}

你可能感兴趣的:(java,mybatis,java,spring,boot)