數據脫敏:姓名、電話號碼等進行字段脫敏,中間部分顯示成**


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

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

  第二種方式是采用面向切面編程AOP相類似的方式,只需要寫一個方法,然后在方法上加一個自定義類注解,在過濾的屬性上加上類型注解就解決。

這里主要講第二種方式 實現步驟為:

  ①定義脫敏的類型

  ②自定義類或方法的注解和字段的注解

  ③定義返回數據的格式

  ④實現脫敏的規則

  ⑤在字段和類上使用注解,聲明脫敏的字段

  ⑥測試調用接口 獲得結果

本博客涉及到的知識:

  ①如何自定義注解,以及各注解代表的含義

  ②了解特定注解@ControllerAdvice的含義以及接口ResponseBodyAdvice的機制

  ③反射獲取數據,解析數據

  ④實現脫敏的邏輯

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 標記這個注解是繼承於哪個注解類

 

 

 

 

 

 

 

 

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM