1. 前言
在日常開發(fā)中,身份證號、手機(jī)號、卡號、客戶號等個人信息都需要進(jìn)行數(shù)據(jù)脫敏。否則容易造成個人隱私泄露,客戶資料泄露,給不法分子可乘之機(jī)。但是數(shù)據(jù)脫敏不是把敏感信息隱藏起來,而是看起來像真的一樣,實際上不能是真的。我以前的公司就因為不重視脫敏,一名員工在離職的時候通過后臺的導(dǎo)出功能導(dǎo)出了核心的客戶資料賣給了競品,給公司造成了重大的損失。當(dāng)然這里有數(shù)據(jù)管理的原因,但是脫敏仍舊是不可忽略的一環(huán),脫敏可以從一定程度上保證數(shù)據(jù)的合規(guī)使用。下面就是一份經(jīng)過脫敏的數(shù)據(jù):
最近在研究Mybatis的插件,所以考慮能不能在ORM中搞一搞脫敏,所以就嘗試了一下,這里分享一下思路。借此也分享一下Mybatis插件開發(fā)的思路。
2.1 Mybatis 插件接口
Mybatis中使用插件,需要實現(xiàn)接口org.apache.ibatis.plugin.Interceptor
,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; default Object plugin(Object target) { return Plugin.wrap(target, this ); } default void setProperties(Properties properties) { // NOP } } |
這里其實最核心的是Object intercept(Invocation invocation)
方法,這是我們需要實現(xiàn)的方法。
2.2 Invocation 對象
那么核心方法中的Invocation
是個什么概念呢?
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
|
public class Invocation { private final Object target; private final Method method; private final Object[] args; public Invocation(Object target, Method method, Object[] args) { this .target = target; this .method = method; this .args = args; } public Object getTarget() { return target; } public Method getMethod() { return method; } public Object[] getArgs() { return args; } public Object proceed() throws InvocationTargetException, IllegalAccessException { return method.invoke(target, args); } } |
這個東西包含了四個概念:
- target 攔截的對象
- method 攔截target中的具體方法,也就是說Mybatis插件的粒度是精確到方法級別的。
- args 攔截到的參數(shù)。
- proceed 執(zhí)行被攔截到的方法,你可以在執(zhí)行的前后做一些事情。
2.3 攔截簽名
既然我們知道了Mybatis插件的粒度是精確到方法級別的,那么疑問來了,插件如何知道輪到它工作了呢?
所以Mybatis設(shè)計了簽名機(jī)制來解決這個問題,通過在插件接口上使用注解@Intercepts
標(biāo)注來解決這個問題。
1
2
3
|
@Intercepts ( @Signature (type = ResultSetHandler. class , method = "handleResultSets" , args = {Statement. class }) |
就像上面一樣,事實上就等于配置了一個Invocation
。
2.4 插件的作用域
那么問題又來了,Mybatis插件能攔截哪些對象,或者說插件能在哪個生命周期階段起作用呢?它可以攔截以下四大對象:
- Executor 是SQL執(zhí)行器,包含了組裝參數(shù),組裝結(jié)果集到返回值以及執(zhí)行SQL的過程,粒度比較粗。
- StatementHandler 用來處理SQL的執(zhí)行過程,我們可以在這里重寫SQL非常常用。
- ParameterHandler 用來處理傳入SQL的參數(shù),我們可以重寫參數(shù)的處理規(guī)則。
- ResultSetHandler 用于處理結(jié)果集,我們可以重寫結(jié)果集的組裝規(guī)則。
你需要做的就是明確的你的業(yè)務(wù)需要在上面四個對象的哪個處理階段攔截處理即可。
2.5 MetaObject
Mybatis提供了一個工具類org.apache.ibatis.reflection.MetaObject
。它通過反射來讀取和修改一些重要對象的屬性。我們可以利用它來處理四大對象的一些屬性,這是Mybatis插件開發(fā)的一個常用工具類。
- Object getValue(String name) 根據(jù)名稱獲取對象的屬性值,支持OGNL表達(dá)式。
- void setValue(String name, Object value) 設(shè)置某個屬性的值。
- Class<?> getSetterType(String name) 獲取setter方法的入?yún)㈩愋汀?/li>
- Class<?> getGetterType(String name) 獲取getter方法的返回值類型。
通常我們使用SystemMetaObject.forObject(Object object)
來實例化MetaObject
對象。你會在接下來的實戰(zhàn)DEMO中看到我使用它。
3. Mybatis 脫敏插件實戰(zhàn)
接下來我就把開頭的脫敏需求實現(xiàn)一下。首先需要對脫敏字段進(jìn)行標(biāo)記并確定使用的脫敏策略。
編寫脫敏函數(shù):
1
2
3
4
5
6
7
8
|
/** * 具體策略的函數(shù) * @author felord.cn * @since 11:24 **/ public interface Desensitizer extends Function<String,String> { } |
編寫脫敏策略枚舉:
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
|
/** * 脫敏策略. * * @author felord.cn * @since 11 :25 */ public enum SensitiveStrategy { /** * Username sensitive strategy. */ USERNAME(s -> s.replaceAll( "(\\S)\\S(\\S*)" , "$1*$2" )), /** * Id card sensitive type. */ ID_CARD(s -> s.replaceAll( "(\\d{4})\\d{10}(\\w{4})" , "$1****$2" )), /** * Phone sensitive type. */ PHONE(s -> s.replaceAll( "(\\d{3})\\d{4}(\\d{4})" , "$1****$2" )), /** * Address sensitive type. */ ADDRESS(s -> s.replaceAll( "(\\S{8})\\S{4}(\\S*)\\S{4}" , "$1****$2****" )); private final Desensitizer desensitizer; SensitiveStrategy(Desensitizer desensitizer) { this .desensitizer = desensitizer; } /** * Gets desensitizer. * * @return the desensitizer */ public Desensitizer getDesensitizer() { return desensitizer; } } |
編寫脫敏字段的標(biāo)記注解:
1
2
3
4
5
|
@Retention (RetentionPolicy.RUNTIME) @Target (ElementType.FIELD) public @interface Sensitive { SensitiveStrategy strategy(); } |
我們的返回對象中如果某個字段需要脫敏,只需要通過標(biāo)記就可以了。例如下面這樣:
1
2
3
4
5
6
7
8
9
|
@Data public class UserInfo { private static final long serialVersionUID = -8938650956516110149L; private Long userId; @Sensitive (strategy = SensitiveStrategy.USERNAME) private String name; private Integer age; } |
然后就是編寫插件了,我可以確定的是需要攔截的是ResultSetHandler
對象的handleResultSets
方法,我們只需要實現(xiàn)插件接口Interceptor
并添加簽名就可以了。全部邏輯如下:
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
|
@Slf4j @Intercepts ( @Signature (type = ResultSetHandler. class , method = "handleResultSets" , args = {Statement. class })) public class SensitivePlugin implements Interceptor { @SuppressWarnings ( "unchecked" ) @Override public Object intercept(Invocation invocation) throws Throwable { List<Object> records = (List<Object>) invocation.proceed(); // 對結(jié)果集脫敏 records.forEach( this ::sensitive); return records; } private void sensitive(Object source) { // 拿到返回值類型 Class<?> sourceClass = source.getClass(); // 初始化返回值類型的 MetaObject MetaObject metaObject = SystemMetaObject.forObject(source); // 捕捉到屬性上的標(biāo)記注解 @Sensitive 并進(jìn)行對應(yīng)的脫敏處理 Stream.of(sourceClass.getDeclaredFields()) .filter(field -> field.isAnnotationPresent(Sensitive. class )) .forEach(field -> doSensitive(metaObject, field)); } private void doSensitive(MetaObject metaObject, Field field) { // 拿到屬性名 String name = field.getName(); // 獲取屬性值 Object value = metaObject.getValue(name); // 只有字符串類型才能脫敏 而且不能為null if (String. class == metaObject.getGetterType(name) && value != null ) { Sensitive annotation = field.getAnnotation(Sensitive. class ); // 獲取對應(yīng)的脫敏策略 并進(jìn)行脫敏 SensitiveStrategy type = annotation.strategy(); Object o = type.getDesensitizer().apply((String) value); // 把脫敏后的值塞回去 metaObject.setValue(name, o); } } } |
然后配置脫敏插件使之生效:
1
2
3
4
|
@Bean public SensitivePlugin sensitivePlugin(){ return new SensitivePlugin(); } |
操作查詢獲得結(jié)果 UserInfo(userId=123123, name=李*龍, age=28)
,成功將指定字段進(jìn)行了脫敏。
補充一句,其實脫敏也可以在JSON序列化的時候進(jìn)行。
4. 總結(jié)
今天對編寫Mybatis插件的一些要點進(jìn)行了說明,同時根據(jù)說明實現(xiàn)了一個脫敏插件。但是請注意一定要熟悉四大對象的生命周期,否則自寫插件可能會造成意想不到的結(jié)果。
到此這篇關(guān)于自己動手編寫一個Mybatis插件之Mybatis脫敏插件的文章就介紹到這了,更多相關(guān)Mybatis脫敏插件內(nèi)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://www.cnblogs.com/felordcn/p/13473844.html