背景
数据库在保存数据时,对于某些敏感数据需要脱敏或者加密处理,如果一个一个的去加显然工作量大而且容易出错,这个时候可以考虑使用拦截器,本文针对的是mybatis-plus作为持久层框架,其他场景未测试。代码如下:
一、查询拦截器
复制代码
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
90package com.sfpay.merchant.service.interceptor; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.sfpay.merchant.service.service.CryptService; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.binding.MapperMethod; 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.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.Map; import java.util.Objects; /** * @describe: 查询拦截器 * 查询条件加密使用方式:使用 @Param("decrypt")注解的自定义类型 * 返回结果解密使用方式: ①在自定义的DO上加上注解 CryptAnnotation ②在需要加解密的字段属性上加上CryptAnnotation * @author: *** * @date: 2021/3/30 17:51 */ @Slf4j @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 QueryInterceptor implements Interceptor { /** * 查询参数名称,ParamMap的key值 */ private static final String DECRYPT = "decrypt"; @Autowired private CryptService cryptService; @Autowired private UpdateInterceptor updateInterceptor; @Override public Object intercept(Invocation invocation) throws Throwable { //获取查询参数,查询条件是否需要加密 Object[] args = invocation.getArgs(); Object parameter = args[1]; Object result = null; //设置执行标识 boolean flag = true; if (parameter instanceof MapperMethod.ParamMap) { Map paramMap = (Map) parameter; if (paramMap.containsKey(DECRYPT)) { Object queryParameter = paramMap.get(DECRYPT); if (updateInterceptor.needToCrypt(queryParameter)) { //执行sql,还原加密后的报文 MappedStatement mappedStatement = (MappedStatement) args[0]; result = updateInterceptor.proceed(invocation, mappedStatement, queryParameter); flag = false; } } } //是否需要执行 if (flag) { result = invocation.proceed(); } if (Objects.isNull(result)) { return null; } // 返回列表数据,循环检查 if (result instanceof ArrayList) { ArrayList resultList = (ArrayList) result; if (CollectionUtils.isNotEmpty(resultList) && updateInterceptor.needToCrypt(resultList.get(0))) { for (Object o : resultList) { cryptService.decrypt(o); } } } else if (updateInterceptor.needToCrypt(result)) { cryptService.decrypt(result); } //返回结果 return result; } }
二、插入和更新拦截器
复制代码
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176package com.sfpay.merchant.service.interceptor; import com.baomidou.mybatisplus.annotation.TableId; import com.sfpay.merchant.common.util.annotation.CryptAnnotation; import com.sfpay.merchant.service.service.CryptService; import org.apache.ibatis.binding.MapperMethod; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.session.defaults.DefaultSqlSession; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Map; import java.util.Objects; import java.util.Optional; /** * @describe: 数据库更新操作拦截器 * 一、支持的使用场景 * ①场景一:通过mybatis-plus BaseMapper自动映射的方法 * ②场景一:通过mapper接口自定义的方法,更新对象为自定义DO * 二、使用方法 * ①在自定义的DO上加上注解 CryptAnnotation * ②在需要加解密的字段属性上加上CryptAnnotation * ③自定义映射方法在需要加解密的自定义DO参数使用@Param("et") * @author: *** * @date: 2021/3/31 17:51 */ @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}) public class UpdateInterceptor implements Interceptor { @Autowired private CryptService cryptService; /** * 更新参数名称,ParamMap的key值 */ private static final String CRYPT = "et"; @Override public Object intercept(Invocation invocation) throws Throwable { //代理类方法参数,该拦截器拦截的update方法有两个参数args = {MappedStatement.class, Object.class} Object[] args = invocation.getArgs(); //获取方法参数 MappedStatement mappedStatement = (MappedStatement) args[0]; Object parameter = args[1]; if (Objects.isNull(parameter)) { //无参数,直接放行 return invocation.proceed(); } // 如果是多个参数或使用Param注解(Param注解会将参数放置在ParamMap中) if (parameter instanceof MapperMethod.ParamMap) { Map paramMap = (Map) parameter; if (paramMap.containsKey(CRYPT)) { Object updateParameter = paramMap.get(CRYPT); if (needToCrypt(updateParameter)) { //执行sql,还原加解密后的报文 return proceed(invocation, mappedStatement, updateParameter); } } } else if (parameter instanceof DefaultSqlSession.StrictMap) { //不知道是啥意思,直接过 return invocation.proceed(); } else if (needToCrypt(parameter)) { //执行sql,还原加解密后的报文 return proceed(invocation, mappedStatement, parameter); } //其他场景直接放行 return invocation.proceed(); } /** * 执行sql,还原加解密后的报文 * * @param invocation * @param mappedStatement * @param parameter * @return * @throws IllegalAccessException * @throws InstantiationException * @throws InvocationTargetException */ Object proceed(Invocation invocation, MappedStatement mappedStatement, Object parameter) throws IllegalAccessException, InstantiationException, InvocationTargetException { //先复制一个对象备份数据 Object newInstance = newInstance(parameter); //调用加解密服务 cryptService.encrypt(parameter); //执行操作,得到返回结果 Object result = invocation.proceed(); //把加解密后的字段还原 reductionParameter(mappedStatement, newInstance, parameter); //返回结果 return result; } /** * 先复制一个对象备份数据,便于加解密后还原原报文 * * @param parameter * @return * @throws IllegalAccessException * @throws InstantiationException */ private Object newInstance(Object parameter) throws IllegalAccessException, InstantiationException { Object newInstance = parameter.getClass().newInstance(); BeanUtils.copyProperties(parameter, newInstance); return newInstance; } /** * 把加解密后的字段还原,同时把mybatis返回的tableId返回给参数对象 * * @param mappedStatement * @param newInstance * @param parameter * @throws IllegalAccessException */ private void reductionParameter(MappedStatement mappedStatement, Object newInstance, Object parameter) throws IllegalAccessException { //获取映射语句命令类型 SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); if (SqlCommandType.INSERT == sqlCommandType) { //从参数属性中找到注解是TableId的字段 Field[] parameterFields = parameter.getClass().getDeclaredFields(); Optional<Field> optional = Arrays.stream(parameterFields).filter(field -> field.isAnnotationPresent(TableId.class)).findAny(); if (optional.isPresent()) { Field field = optional.get(); field.setAccessible(true); Object id = field.get(parameter); //覆盖参数加解密的值 BeanUtils.copyProperties(newInstance, parameter); field.set(parameter, id); } else { //覆盖参数加解密的值 BeanUtils.copyProperties(newInstance, parameter); } } else { //覆盖参数加解密的值 BeanUtils.copyProperties(newInstance, parameter); } } /** * 是否需要加解密: * ①是否属于基本类型,void类型和String类型,如果是,不加解密 * ②DO上是否有注解 ③ 属性是否有注解 * * @param object * @return */ public boolean needToCrypt(Object object) { if (object == null) { return false; } Class<?> clazz = object.getClass(); if (clazz.isPrimitive() || object instanceof String) { //基本类型和字符串不加解密 return false; } //获取DO注解 boolean annotationPresent = clazz.isAnnotationPresent(CryptAnnotation.class); if (!annotationPresent) { //无DO注解不加解密 return false; } //获取属性注解 Field[] fields = clazz.getDeclaredFields(); return Arrays.stream(fields).anyMatch(field -> field.isAnnotationPresent(CryptAnnotation.class)); } }
三、注解
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import com.sfpay.merchant.common.constant.EncryptDataTypeEnum; import java.lang.annotation.*; /** * @author *** * @Date 2020/12/30 20:13 * @description 加密注解类 * @Param * @return **/ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) @Documented @Inherited public @interface CryptAnnotation { EncryptDataTypeEnum type() default EncryptDataTypeEnum.OTHER; }
cryptService 为加密服务,怎么实现自己可以根据实际情况来实现。
到此这篇关于mybatis-plus 拦截器敏感字段加解密的实现的文章就介绍到这了,更多相关mybatis-plus 敏感字段加解密内容请搜索靠谱客以前的文章或继续浏览下面的相关文章希望大家以后多多支持靠谱客!
最后
以上就是专注薯片最近收集整理的关于mybatis-plus 拦截器敏感字段加解密的实现的全部内容,更多相关mybatis-plus内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复