返回顶部

Mybatis插件应用之数据脱敏

利用Mybatis插件实现数据脱敏

功能介绍

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

代码说明

  • 2个自定义注解

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/**
 * 自定义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类的加解密工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
 * 加解密工具类
 */
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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);
    }
}
1
2
3
4
5
6
7
8
9
@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测试

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

1
2
3
4
5
6
7
POST http://localhost:8880/api/v1/dork-h/encrypt/insert
Content-Type: application/json
 
{
  "name": "test1010",
  "pwd": "password1010"
}

 搞定~~~

 

 
 
 
posted @   dork-h  阅读(409)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示