zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

Mybatis插件应用之数据脱敏

2023-03-31 10:46:04 时间

利用Mybatis插件实现数据脱敏

功能介绍

利用mybatis中的plugin(拦截器,底层基于jdk动态代理实现),并结合自定义注解,实现对某些重要字段的加密和解密。

代码说明

  • 2个自定义注解

/**
 * 标识需要加解密的字段
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Encrypt {
}
/**
 * 标识需要加解密的类
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SensitiveData {
}
  • 基于mybatis的自定义插件

/**
 * 自定义mybatis拦截器
 */
@Component
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class EncryptInterceptor implements Interceptor {

    /**
     * 加密标识
     */
    private static final int ENCRYPT_DATA = 0;

    /**
     * 解密标识
     */
    private static final int DECRYPT_DATA = 1;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result;
        //根据拦截的方法判断对字段加密还是解密
        if (Objects.equals(invocation.getMethod().getName(), "query")) {
            //对查询结果解密
            result = decryptData(invocation);
        } else {
            //对插入数据加密
            result = encryptData(invocation);
        }

        return result;
    }

    /**
     * 对查询结果解密入口
     * @param invocation
     * @return
     * @throws Exception
     */
    private Object decryptData(Invocation invocation) throws Exception {
        System.out.println("解密...");
        //取出查询的结果
        Object resultObj = invocation.proceed();
        if (Objects.isNull(resultObj)) {
            return null;
        }

        //结果为数组
        if (resultObj instanceof List) {
            List<?> data = (List<?>) resultObj;
            if (CollectionUtils.isEmpty(data) || !findSensitiveAnnotation(data.get(0))) {
                return null;
            }
            for (Object item : data) {
                handle(item, DECRYPT_DATA);
            }
            return resultObj;
        }

        //结果为单个对象
        if (findSensitiveAnnotation(resultObj)) {
            handle(resultObj, DECRYPT_DATA);
        }

        return resultObj;
    }

    /**
     * 对插入数据加密入口
     * @param invocation
     * @return
     * @throws Exception
     */
    private Object encryptData(Invocation invocation) throws Exception {
        System.out.println("加密...");
        Object param = invocation.getArgs()[1];
        if (Objects.isNull(param) || !findSensitiveAnnotation(param)) {
            return null;
        }

        System.out.println("原插入对象:" + param);
        handle(param, ENCRYPT_DATA);
        System.out.println("加密后对象:" + param);
        return invocation.proceed();
    }

    /**
     * 判断类是否包含@SensitiveData注解
     * @param obj
     * @return
     */
    private boolean findSensitiveAnnotation(Object obj) {
        return Objects.nonNull(AnnotationUtils.findAnnotation(obj.getClass(), SensitiveData.class));
    }

    /**
     * 对数据解密或解密
     * @param data
     * @param flag
     * @param <T>
     * @throws Exception
     */
    public <T> void handle(T data, int flag) throws Exception {
        //遍历字段
        for (Field field : data.getClass().getDeclaredFields()) {
            //取出被Encrypt注解的字段
            if (Objects.isNull(field.getAnnotation(Encrypt.class))) {
                continue;
            }

            field.setAccessible(true);
            Object val = field.get(data);
            if (val instanceof String) {
                if (flag == DECRYPT_DATA) {
                    field.set(data, EncryptUtil.decrypt((String) val));
                } else if (flag == ENCRYPT_DATA) {
                    field.set(data, EncryptUtil.encrypt((String) val));
                } else {
                    return;
                }
            }
        }

    }

    @Override
    public Object plugin(Object target) {
        return Interceptor.super.plugin(target);
    }

    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }
}
  • 基于Base64类的加解密工具

/**
 * 加解密工具类
 */
public class EncryptUtil {

    private static final String DEFAULT_V = "6859505890402435";
    private static final String KEY = "***";
    private static final String ALGORITHM = "AES";

    private static SecretKeySpec getKey() {
        byte[] arrBTmp = EncryptUtil.KEY.getBytes();
        // 创建一个空的16位字节数组(默认值为0)
        byte[] arrB = new byte[16];
        for (int i = 0; i < arrBTmp.length && i < arrB.length; i++) {
            arrB[i] = arrBTmp[i];
        }
        return new SecretKeySpec(arrB, ALGORITHM);
    }

    /**
     * 加密
     * @param content
     * @return
     * @throws Exception
     */
    public static String encrypt(String content) throws Exception {
        final Base64.Encoder encoder = Base64.getEncoder();
        SecretKeySpec keySpec = getKey();
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        IvParameterSpec iv = new IvParameterSpec(DEFAULT_V.getBytes());
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
        byte[] encrypted = cipher.doFinal(content.getBytes());
        return encoder.encodeToString(encrypted);
    }

    /**
     * 解密
     * @param content
     * @return
     * @throws Exception
     */
    public static String decrypt(String content) throws Exception {
        final Base64.Decoder decoder = Base64.getDecoder();
        SecretKeySpec keySpec = getKey();
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        IvParameterSpec iv = new IvParameterSpec(DEFAULT_V.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
        byte[] base64 = decoder.decode(content);
        byte[] original = cipher.doFinal(base64);
        return new String(original);
    }
}
  • 3层架构连接mybatis

@RestController
@RequestMapping(value = "/api/v1/dork-h/encrypt")
public class EncryptController {

    @Resource
    EncryptService encryptService;

    @GetMapping(value = "/search")
    public Result testEncrypt() {
        List<User> user = encryptService.getUser();
        return Result.success(user);
    }

    @PostMapping(value = "/insert")
    public Result insert(@RequestBody User user) {
        boolean result = encryptService.insert(user);
        return Result.success(result);
    }
}
@Service
public class EncryptService {

    @Resource
    EncryptMapper encryptMapper;

    public List<User> getUser(){
        List<User> user = encryptMapper.getUser();
        return user;
    }

    public boolean insert(User user) {
        return encryptMapper.insert(user);
    }
}
@Mapper
@Component
public interface EncryptMapper {

    List<User> getUser();

    boolean insert(User user);

}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.com.dork.study.encrypt.EncryptMapper">
    <insert id="insert" parameterType="cn.com.dork.test.User">
        insert into test(name,pwd) values (#{name},#{pwd})
    </insert>

    <select id="getUser" resultType="cn.com.dork.test.User">
        SELECT id, name, pwd
        FROM test
    </select>
</mapper>
  • http测试

GET http://localhost:8880/api/v1/dork-h/encrypt/search

POST http://localhost:8880/api/v1/dork-h/encrypt/insert
Content-Type: application/json

{
  "name": "test1010",
  "pwd": "password1010"
}

 搞定~~~