# 基于spring aop拦截器对数据库字段进行加密解密脱敏

 

1. 通过aop方式 + 方法/实体属性注解 + 反射处理加解密。 

 

  1. 基于aop方式实现,不受限与持久层框架限制,网上有些使用mybatis拦截器的方式实现(有缺点,对复杂的sql不支持,所以重新基于aop方式实现) ,代码已在mybatis plus中测试完毕。

 

2.使用方法:

  2.1: 和java validation的用法基本一样(1.支持方法中的参数标记@DbEncryptField、2.支持类中的属性标记@DbEncryptField)

 

# 工作原理: aop层对“Mapper”层进行切入,切入之后:

 

1. // 对参数对象中的字段进行处理(对带有@DbEncryptField注解的字段处理)-加密

2. // 执行原始方法

3. // 对参数对象中的字段进行处理(对带有@DbEncryptField注解的字段处理)-解密

4. // 对返回结果进行解密处理,以返回原始值给调用方

 

# 加密处理后,原字段根据加密的算法进行扩容,比如sm4加密后推荐对原字段长度扩充10倍,以存储密文

 

# 代码实现

 

## 定义注解

 

package com.xxx;


import java.lang.annotation.*;

/***
* *---------------------------
* @Description: 标记需加密、解密的字段
* ***---------------------------
* @author: wulingming
* @date: 2026年01月06日 14:41
* @version: v1.0
* ****---------------------------
*/
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DbEncryptField {

//    /***
//    * *---------------------------
//    * @Description: 加密方式,有多种加密方式时可以指定
//    * **---------------------------
//     * @return EncryptTypeEnum
//    * ***---------------------------
//    * @author: wulingming
//    * @date: 2026年01月06日 14:41
//    * @version: v1.0
//    * ****---------------------------
//    */
//    EncryptTypeEnum algorithm() default EncryptTypeEnum.SM4;

}

 

## aop层切面

 

package com.broker.framework.mybatis.core.aspect;

import com.broker.framework.mybatis.core.util.DbEncryptFieldUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;


/**
 *
 * @desc 拦截方法的入参信息和返回信息
 */
@Aspect
@Configuration
@Slf4j
public class DbEncryptFieldAop {

    // 使用配置的密钥
    @Value("${broker.mybatis.encrypt.encrypt-key}")
    private String encryptKey;

    @Pointcut(value = "execution(* com.broker..*Mapper.*(..))")
    private void mapper() {
    }


    /***
     * *---------------------------
     * @Description: 拦截Mapper方法,对带有@DbEncryptField注解的字段和参数进行加解密处理
     *              1. 在方法执行前,对方法参数和参数对象中的加密字段进行加密
     *              2. 执行原始方法
     *              3. 在方法执行后,对参数和返回结果进行解密,以恢复原始值
     * ***---------------------------
     * @author: wulingming
     * @date: 2026年01月05日 14:37
     * @version: v1.0
     * ****---------------------------
     */
    @Around(value = "mapper()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        // 校验加密密钥
        if (!StringUtils.hasText(encryptKey)) {
            log.error("加密密钥未配置或为空,无法进行加解密操作");
            throw new IllegalArgumentException("加密密钥未配置或为空");
        }

        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Parameter[] parameters = method.getParameters();

        // 获取方法参数值
        Object[] objectsArray = joinPoint.getArgs();


        // 对参数对象中的字段进行处理(对带有@DbEncryptField注解的字段处理)-加密
        DbEncryptFieldUtil.encryptOrDecryptParameters(parameters, objectsArray, DbEncryptFieldUtil.OperationEnum.ENCRYPT, encryptKey);

        // 执行原始方法
        Object resultObj = joinPoint.proceed(objectsArray);

        // 对参数对象中的字段进行处理(对带有@DbEncryptField注解的字段处理)-解密
        DbEncryptFieldUtil.encryptOrDecryptParameters(parameters, objectsArray, DbEncryptFieldUtil.OperationEnum.DECRYPT, encryptKey);

        // 检查resultObj方法返回结果是否和入参的内存地址一致,如果一致则不再解密避免重复解密(在上一步的参数解密已经处理过一次)
        if (Arrays.stream(objectsArray).anyMatch(param -> resultObj == param)) {
            return resultObj;
        }

        Object[] objects = {resultObj};
        // 对返回结果进行解密处理,以返回原始值给调用方
        DbEncryptFieldUtil.encryptOrDecryptParameters(null, objects, DbEncryptFieldUtil.OperationEnum.DECRYPT, encryptKey);

        return objects[0];
    }


}

 

 

 

 

## 加解密处理工具(加密方式默认使用sm4,如需替换可手动更改doEncryptOrDecryptSm4ToString 的实现)

 

 

package com.broker.framework.mybatis.core.util;

import cn.hutool.core.collection.CollUtil;
import com.broker.framework.common.crypt.annotation.DbEncryptField;
import com.broker.framework.common.util.sm4.SM4Util;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;

import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.function.Consumer;


/**
 * @desc 对带有@DbEncryptField注解({@link DbEncryptField})的字段、方法参数进行加密、解密 。
 * 通过aop切面切入,{@link com.broker.framework.mybatis.core.aspect.DbEncryptFieldAop}
 *
 */
@Slf4j
public class DbEncryptFieldUtil {

    /**
     * The enum Encrypt operation.
     */
    public enum OperationEnum {
        /**
         *Encrypt operation.
         */
        ENCRYPT,
        /**
         *Decrypt operation.
         */
        DECRYPT
    }

    /***
     * *---------------------------
     * @Description: 对参数进行加解密
     * **---------------------------
     * @param parameters 参数对象数组
     * @param paramValues 参数值数组
     * @param operationEnum 加解密操作枚举
     * @param secretHexKey 16进制密钥(忽略大小写)
     * @return void
     * ***---------------------------
     * @author: wulingming
     * @date: 2026年01月05日 11:44
     * @version: v1.0
     * ****---------------------------
     */
    public static void encryptOrDecryptParameters(Parameter[] parameters, Object[] paramValues, OperationEnum operationEnum, String secretHexKey) {
        for (int i = 0; i < paramValues.length; i++) {
            //返回结果没有参数名,只有返回值,这里要做空值检查。
            Parameter parameter = parameters == null ? null : parameters[i];
            Object paramValue = paramValues[i];

            boolean inheritedAnnotation = parameter != null && parameter.isAnnotationPresent(DbEncryptField.class);
            //参数名
            String parameterName = parameter == null ? "方法返回结果" : parameter.getName();

            if (paramValue != null) {
                int finalI = i;
                encryptOrDecryptParameter(parameterName, paramValue, operationEnum, secretHexKey, inheritedAnnotation, s -> {
                    paramValues[finalI] = s;
                });
            } else {
                log.trace("[{}] 参数索引: {}, 参数名: {} 的值为 null,跳过处理",
                        operationEnum, i, parameterName);
            }
        }
    }

    /***
     * *---------------------------
     * @Description: 处理参数对象,对带有@DbEncryptField注解的字段进行加密、解密   (递归方法主体)
     * **---------------------------
     * @param fieldName 字段名
     * @param fieldValue 字段值
     * @param operationEnum 加解密操作枚举
     * @param secretHexKey 16进制密钥(忽略大小写)
     * @param inheritedAnnotation 是否继承了父级的@DbEncryptField注解
     * @param resultValueConsumer 结果值消费者
     * @return void
     * ***---------------------------
     * @author: wulingming
     * @date: 2026年01月06日 14:19
     * @version: v1.0
     * ****---------------------------
     */
    public static void encryptOrDecryptParameter(@Nullable String fieldName, @Nullable Object fieldValue, OperationEnum operationEnum, String secretHexKey, boolean inheritedAnnotation, Consumer<String> resultValueConsumer) {
        Assert.notNull(operationEnum, "operationEnum cannot be null");
        Assert.notNull(secretHexKey, "secretHexKey cannot be null");
        Assert.notNull(resultValueConsumer, "resultValueConsumer cannot be null");

        if (fieldValue == null) {
            log.trace("[{}] 字段: {} 值为 null,跳过处理", operationEnum, fieldName);
            return;
        }

        if (isPrimitiveOrWrapper(fieldValue.getClass())) {
            if (fieldValue instanceof String) {
                if (inheritedAnnotation) {
                    String resultValue = doEncryptOrDecryptSm4ToString(fieldName, (String) fieldValue, operationEnum, secretHexKey);
                    resultValueConsumer.accept(resultValue);
                } else {
                    log.trace("[{}] 字段: {} (值: {}) 未标记 @DbEncryptField 注解,跳过处理",
                            operationEnum, fieldName, fieldValue);
                }
            } else {
                log.trace("[{}] 字段: {} (值: {}, 类型: {}) 是基本类型且非字符串,跳过处理",
                        operationEnum, fieldName, fieldValue, fieldValue.getClass().getSimpleName());
            }
        }
        // 检查对象是否是集合
        else if (fieldValue instanceof Collection) {
            Collection<Object> collection = (Collection<Object>) fieldValue;
            if (CollUtil.isNotEmpty(collection)) {
                List<Object> updatedList = new ArrayList<>();
                Iterator<Object> iterator = collection.iterator();
                int i = 0;
                while (iterator.hasNext()) {
                    Object item = iterator.next();
                    encryptOrDecryptParameter(fieldName + "[" + i + "]", item, operationEnum, secretHexKey, inheritedAnnotation, s -> {
                        //先存放到list中,迭代完再添加到原集合中
                        updatedList.add(s);
                        iterator.remove();
                    });
                    i++;
                }
                if (!updatedList.isEmpty()){
                    //迭代完之后替换值
                    collection.addAll(updatedList);
                }
            } else {
                log.trace("[{}] 集合字段: {} 为空,跳过处理",
                        operationEnum, fieldName);
            }
        }
        // 检查对象是否是Map
        else if (fieldValue instanceof Map) {
            Map<Object, Object> map = (Map<Object, Object>) fieldValue;

            if (CollUtil.isNotEmpty(map)) {
                Map<Object, Object> updateMap = new HashMap<>();
                map.forEach((key, value) -> encryptOrDecryptParameter(fieldName + "." + key, value, operationEnum, secretHexKey, inheritedAnnotation, s -> {
                    updateMap.put(key, s);
                }));
                if (!updateMap.isEmpty()){
                    //迭代完之后替换值
                    map.putAll(updateMap);
                }
            } else {
                log.trace("[{}] Map字段: {} 为空,跳过处理",
                        operationEnum, fieldName);
            }
        }
        // 普通对象
        else {
            encryptOrDecryptObject(fieldName, fieldValue, operationEnum, secretHexKey);
        }
    }


    /**
     * 处理对象中的字段,对带有@DbEncryptField注解的字段进行加解密
     *
     * @param fieldName     字段名
     * @param fieldValue    要处理的对象
     * @param operationEnum 加解密操作类型
     * @param secretHexKey  加密密钥
     */
    @SneakyThrows
    public static void encryptOrDecryptObject(String fieldName, Object fieldValue, OperationEnum operationEnum, String secretHexKey) {
        if (fieldValue == null) {
            log.trace("[{}] 字段对象: {} 值为 null,跳过处理",
                    operationEnum, fieldName);
            return;
        }

        //兼容外部某些jar包。
        boolean isUnmodifiedJar = false;
        String className = fieldValue.getClass().getName();
        if (className.startsWith("com.baomidou.mybatisplus.extension.plugins.pagination.Page")//兼容mybatis-plus分页对象
        ) {
            isUnmodifiedJar = true;
        }

        Class<?> fieldValueClazz = fieldValue.getClass();
        Field[] subFields = fieldValueClazz.getDeclaredFields();

        for (Field subField : subFields) {
            subField.setAccessible(true);
            Object subFieldValue = subField.get(fieldValue);
            String currentFieldName = fieldName + "." + subField.getName();

            // 检查字段是否有@DbEncryptField注解
            boolean hasAnnotation = subField.isAnnotationPresent(DbEncryptField.class);

            if (hasAnnotation || isUnmodifiedJar) {
                encryptOrDecryptParameter(currentFieldName, subFieldValue, operationEnum, secretHexKey, true, s -> {
                    try {
                        subField.set(fieldValue, s);
                    } catch (IllegalAccessException e) {
                        log.error("[{}] 设置字段值失败: {}.{},错误信息: {}",
                                operationEnum, fieldValue.getClass().getSimpleName(), subField.getName(), e.getMessage(), e);
                        throw new RuntimeException(e);
                    }
                });
            } else {
                log.trace("[{}] 字段: {} (值: {}, 类型: {}) 未标记 @DbEncryptField 注解,跳过处理",
                        operationEnum, currentFieldName, subFieldValue, subField.getType().getSimpleName());
            }

        }
    }

    /***
     * *---------------------------
     * @Description: 对参数对象进行加解密处理,返回加密、解密的结果
     * ***---------------------------
     * @author: wulingming
     * @date: 2026年01月05日 15:58
     * @version: v1.0
     * ****---------------------------
     */
    public static String doEncryptOrDecryptSm4ToString(String fieldName, String fieldValue, OperationEnum operationEnum, String secretHexKey) {
        if (fieldValue != null && !fieldValue.isEmpty()) {
            String resultValue;
            // 使用SM4进行加密或解密
            if (operationEnum == OperationEnum.ENCRYPT) {
                resultValue = SM4Util.encryptEcb(fieldValue, secretHexKey);
                log.trace("[{}] 字段: {} 加密完成: {} -> {}",
                        operationEnum, fieldName, fieldValue, resultValue);
            } else { // DECRYPT
                resultValue = SM4Util.decryptEcb(fieldValue, secretHexKey);
                log.trace("[{}] 字段: {} 解密完成: {} -> {}",
                        operationEnum, fieldName, fieldValue, resultValue);
            }
            return resultValue;
        } else {
            log.trace("[{}] 字段: {} 值为 {},跳过加解密处理",
                    operationEnum, fieldName, fieldValue);
            return fieldValue;
        }
    }

    /**
     * 判断是否为基础类型或包装类型
     */
    private static boolean isPrimitiveOrWrapper(Class<?> clazz) {
        return clazz.isPrimitive() ||
                clazz == String.class ||
                clazz == Boolean.class || clazz == Character.class ||
                clazz == Byte.class || clazz == Short.class ||
                clazz == Integer.class || clazz == Long.class ||
                clazz == Float.class || clazz == Double.class;
    }
}

 

posted on 2026-01-07 11:02  花开浪漫拾  阅读(14)  评论(0)    收藏  举报