在平常開發中,身份證號、手機號、卡號、客戶號等我的信息都須要進行數據脫敏。不然容易形成我的隱私泄露,客戶資料泄露,給不法分子可乘之機。可是數據脫敏不是把敏感信息隱藏起來,而是看起來像真的同樣,實際上不能是真的。我之前的公司就由於不重視脫敏,一名員工在離職的時候經過後臺的導出功能導出了核心的客戶資料賣給了競品,給公司形成了重大的損失。固然這裏有數據管理的緣由,可是脫敏仍舊是不可忽略的一環,脫敏能夠從必定程度上保證數據的合規使用。下面就是一份通過脫敏的數據:java
最近在研究Mybatis的插件,因此考慮能不能在ORM中搞一搞脫敏,因此就嘗試了一下,這裏分享一下思路。藉此也分享一下Mybatis插件開發的思路。apache
Mybatis中使用插件,須要實現接口org.apache.ibatis.plugin.Interceptor
,以下所示:app
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)
方法,這是咱們須要實現的方法。ide
那麼核心方法中的Invocation
是個什麼概念呢?函數
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); } }
這個東西包含了四個概念:工具
既然咱們知道了Mybatis插件的粒度是精確到方法級別的,那麼疑問來了,插件如何知道輪到它工做了呢?this
因此Mybatis設計了簽名機制來解決這個問題,經過在插件接口上使用註解@Intercepts
標註來解決這個問題。spa
@Intercepts(@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}))
就像上面同樣,事實上就等於配置了一個Invocation
。插件
那麼問題又來了,Mybatis插件能攔截哪些對象,或者說插件能在哪一個生命週期階段起做用呢?它能夠攔截如下四大對象:設計
你須要作的就是明確的你的業務須要在上面四個對象的哪一個處理階段攔截處理便可。
Mybatis提供了一個工具類org.apache.ibatis.reflection.MetaObject
。它經過反射來讀取和修改一些重要對象的屬性。咱們能夠利用它來處理四大對象的一些屬性,這是Mybatis插件開發的一個經常使用工具類。
一般咱們使用SystemMetaObject.forObject(Object object)
來實例化MetaObject
對象。你會在接下來的實戰DEMO中看到我使用它。
接下來我就把開頭的脫敏需求實現一下。首先須要對脫敏字段進行標記並肯定使用的脫敏策略。
編寫脫敏函數:
/** * 具體策略的函數 * @author felord.cn * @since 11:24 **/ public interface Desensitizer extends Function<String,String> { }
編寫脫敏策略枚舉:
/** * 脫敏策略. * * @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; } }
編寫脫敏字段的標記註解:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Sensitive { SensitiveStrategy strategy(); }
咱們的返回對象中若是某個字段須要脫敏,只須要經過標記就能夠了。例以下面這樣:
@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
方法,咱們只須要實現插件接口Interceptor
並添加簽名就能夠了。所有邏輯以下:
@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(); // 對結果集脫敏 records.forEach(this::sensitive); return records; } private void sensitive(Object source) { // 拿到返回值類型 Class<?> sourceClass = source.getClass(); // 初始化返回值類型的 MetaObject MetaObject metaObject = SystemMetaObject.forObject(source); // 捕捉到屬性上的標記註解 @Sensitive 並進行對應的脫敏處理 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); // 獲取對應的脫敏策略 並進行脫敏 SensitiveStrategy type = annotation.strategy(); Object o = type.getDesensitizer().apply((String) value); // 把脫敏後的值塞回去 metaObject.setValue(name, o); } } }
而後配置脫敏插件使之生效:
@Bean public SensitivePlugin sensitivePlugin(){ return new SensitivePlugin(); }
操做查詢得到結果 UserInfo(userId=123123, name=李*龍, age=28)
,成功將指定字段進行了脫敏。
補充一句,其實脫敏也能夠在 JSON序列化的時候進行。
今天對編寫Mybatis插件的一些要點進行了說明,同時根聽說明實現了一個脫敏插件。可是請注意必定要熟悉四大對象的生命週期,不然自寫插件可能會形成意想不到的結果。插件能夠關注:碼農小胖哥 回覆關鍵字 sensitive 進行獲取。若是你以爲有用請無情的點贊。
關注公衆號:Felordcn 獲取更多資訊