資料脫敏:姓名、電話號碼等進行欄位脫敏,中間部分顯示成**

一一火柴一一發表於2021-08-21

  在前端展示時,有時需要將名字、電話號碼、身份證等敏感資訊過濾展示(脫敏),這種一般需要後端處理,提前將敏感資訊過濾換成**的字樣。

  第一種方式是在每個頁面展示時,去過濾,但是需要改動的地方非常多。實用性不強;

  第二種方式是通過面向切面程式設計AOP的方式,只需要寫一個方法,然後在方法上加一個自定義註解就解決。

這裡主要講第二種方式

1.自定義註解

  宣告一個列舉脫敏型別

    /**
     * 資料脫敏型別
     */
    public enum DesensitizeType {

        NAME, // 名稱
        ID_CARD_18, //身份證 18
        EMAIL,//email
        MOBILE_PHONE; //手機號
    }

 

   宣告脫敏的欄位 的註解(用在欄位上)

    /**
     * 標記欄位 使用何種策略來脫敏
     */
    @Documented
    @Retention(value = RetentionPolicy.RUNTIME)
    @Target(value = {ElementType.FIELD})
    @Inherited
    public @interface Desensitize {

        DesensitizeType type();
    }

 

   宣告脫敏的方法或類的註解

    /**
     * 標記在類、方法上,是否需要脫敏
     */
    @Documented
    @Retention(value = RetentionPolicy.RUNTIME)
    @Target(value={ElementType.METHOD, ElementType.TYPE})
    @Inherited //說明子類可以繼承父類中的該註解
    public @interface DesensitizeSupport {

    }

 

2.實現資料脫敏

  定義響應的物件格式

/**
 * 響應實體
 */
public class ResResult {
    /**
     * 編碼
     */
    private String code;
    /**
     * 提示資訊
     */
    private String message;
    /**
     * 資料
     */
    private Object data;

    //get //set...
}

 

  資料的model,對要脫敏的欄位加註解@Desensitize和脫敏型別DesensitizeType

public class UserModel implements Serializable {
    /**
     * 姓名
     */
    @Desensitize(type = DesensitizeType.NAME)
    private String name;

    private Integer age;

    private String desc;
    /**
     * 電話號碼
     */
    @Desensitize(type = DesensitizeType.MOBILE_PHONE)
    private String telNumber;

    //get //set...
}

 

   controller層,在類或者方法上加註解@DesensitizeSupport 表示該類或方法支援脫敏

@RestController
@RequestMapping("/test")
@DesensitizeSupport
public class UserController {
    
    @Autowired
    private IUserService iUserService;

    @GetMapping(value = "/listuser")
    public ResResult testHello() {
        ResResult result = new ResResult();
        List<UserModel> list = iUserService.listUser();
        result.setData(list);
        return result;
    }
}

 

   Service層

@Service
public class UserServiceImpl implements IUserService {

    @Override
    public List<UserModel> listUser() {
        UserModel user = new UserModel();
        user.setName("李四");
        user.setAge(123);
        ArrayList<UserModel> list = new ArrayList<>();
        list.add(user);
        return list;
    }
}

 

   有了以上的部分後,還不會進行脫敏,還需要加上脫敏的具體操作。在Controller中執行了return語句後,在返回到前端之前,會執行如下程式碼進行脫敏:

/**
 * 脫敏工具類
 */
public class DesensitizeUtils {

    public static void main(String[] args) {
        String name = "李明";
        System.out.println(repVal(name, 1, 1));
    }
    
    public static String dataMasking(DesensitizeType type, String oldValue) {
        String newVal = null;
        switch (type) {
            case NAME:
                newVal = repVal(oldValue, 1, 1);
                break;
            case ID_CARD_18:
                break;
            case EMAIL:
                break;
            case MOBILE_PHONE:
                break;
        }
        return newVal;
    }
    /**
     * 字元替換
     * @param val
     * @param beg
     * @param end
     * @return
     */
    public static String repVal(String val, int beg, int end) {
        if (StringUtils.isEmpty(val)) {
            return null;
        }
        String name = val.substring(0, beg);
        int length = val.length();
        if (length > 2 && length > end) {
            return name + "**" + val.substring(length-end);
        } else if (length == 2) {
            return name + "*";
        }
        return val;
    }
}

 

 

/**
 * 統一處理 返回值/響應體
 */
@ControllerAdvice
public class DesensitizeResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    private final static Logger logger = LoggerFactory.getLogger(DesensitizeResponseBodyAdvice.class);

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        AnnotatedElement annotatedElement = returnType.getAnnotatedElement();
        //1.首先判斷該方法是否存在@DesensitizeSupport註解
        //2.判斷類上是否存在
        Method method = returnType.getMethod();
        DesensitizeSupport annotation = method.getAnnotation(DesensitizeSupport.class);
        DesensitizeSupport clazzSup = method.getDeclaringClass().getAnnotation(DesensitizeSupport.class);
        return annotation != null || clazzSup != null;
    }

    /**
     *
     * @param body
     * @param returnType
     * @param selectedContentType
     * @param selectedConverterType
     * @param request
     * @param response
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        logger.debug("Test ResponseBodyAdvice ==> beforeBodyWrite:" + body.toString() + ";" + returnType);
        Class<?> childClazz = body.getClass();
        Field childField = null;
        List filedValue = null;
        try {
            //獲取資料
            childField = childClazz.getDeclaredField("data");
            //設定可訪問
            childField.setAccessible(true);
            Object objs = childField.get(body);

            if (!(objs instanceof List)) {
                logger.debug("這不是List型別");
                return body;
            }
            filedValue = (List) objs;
            //對值進行脫敏
            for (Object obj : filedValue) {
                dealValue(obj);
            }
        } catch (NoSuchFieldException e) {
            logger.error("未找到資料; message:" + e.getMessage());
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            logger.error("處理異常; message:" + e.getMessage());
            e.printStackTrace();
        }
        return body;
    }

    public void dealValue(Object obj) throws IllegalAccessException {
        Class<?> clazz = obj.getClass();
        //獲取奔雷和父類的屬性
        List<Field> fieldList = getAllFields(clazz);
        for (Field field : fieldList) {
            //獲取屬性上的註解
            Desensitize annotation = field.getAnnotation(Desensitize.class);
            if (annotation == null) {
                continue;
            }
            Class<?> type = field.getType();
            //判斷屬性的型別
            if (String.class != type) {
                //非字串的型別 直接返回
                continue;
            }
            //獲取脫敏型別  判斷是否脫敏
            DesensitizeType annotType = annotation.type();
            field.setAccessible(true);
            String oldValue = (String) field.get(obj);
            String newVal = DesensitizeUtils.dataMasking(annotType, oldValue);
            field.set(obj, newVal);
        }
    }

    /**
     * 獲取所有的欄位(包括父類的)
     * @param clazz
     * @return
     */
    public List<Field> getAllFields(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            Field[] declaredFields = clazz.getDeclaredFields();
            fieldList.addAll(Arrays.asList(declaredFields));
            //獲取父類,然後獲取父類的屬性
            clazz = clazz.getSuperclass();
        }
        return fieldList;
    }
}

 

3.結果

  響應的結果,我們期待的兩個字的名稱【李四】會【李*】,三個字或三個以上的【李小明】會變成【李**明】(規則可自己進行設定)

 

注:在Controller層執行了return語句後,在返回到前端之前 會執行DesensitizeResponseBodyAdvice類中的supports和beforeBodyWrite方法,其中在類上有一個很重要的註解@ControllerAdvice和很重要的介面ResponseBodyAdvice,這兩個結合在一起,就具有統一處理返回值/響應體的功能。(相當於一個攔截器)

①@ControllerAdvice註解,這是一個Controller的增強型註解,可以實現三方面的功能:

  1. 全域性異常處理
  2. 全域性資料繫結
  3. 全域性資料預處理

介面ResponseBodyAdvice

  繼承了該介面,需要實現兩個方法,supports和beforeBodyWrite方法。在supports方法返回為true後,才會執行beforeBodyWrite方法。其中beforeBodyWrite方法中的body就是響應物件response中的響應體,可以對響應體做統一的處理,比如加密、簽名、脫敏等操作。

 

這裡簡單講解一下其中的註解

使用【@interface】是自定義一個註解,通常自定義的註解上面還有其他註解,如以下幾個:

@Documented 表示標記這個註解是否會包含在文件中
@Retention 標識這個註解怎麼儲存,有三種狀態,value = RetentionPolicy.RUNTIME 表示不僅保留在原始碼中,也保留在class中,並且在執行時可以訪問;
       SOURCE 表示只保留在原始碼中,當在class檔案中時被遺棄;CLASS 表示保留在class檔案中,但jvm載入class檔案時被遺棄。
@Target 標註這個註解屬於Java哪個成員,通常有屬類、方法;欄位;引數;包等
@Inherited 標記這個註解是繼承於哪個註解類

 

 

 

 

 

 

 

 

 若需要完整的程式碼,請點【推薦】,然後留言。或覺得博文不錯 也請推薦留言,感謝你的支援。

 

相關文章