首頁 > 軟體

SpringBoot專案中新增脫敏功能的範例程式碼

2022-12-01 14:02:26

SpringBoot專案中新增脫敏功能

專案背景

目前正在開發一個SpringBoot專案,此專案有Web端和微信小程式端。web端提供給工作人員使用,微信小程式提供給群眾進行預約操作。專案中有部分敏感資料需要脫敏傳遞給微信小程式,給與群眾檢視。

專案需求描述

專案中,由於使用端有兩個,對於兩個端的資料許可權並不一樣。Web端可以檢視所有資料,小程式端只能檢視脫敏後的資料。

需要開發一個通用脫敏功能

  • 手動進行脫敏操作
  • 支援多種物件,
  • 支援不同欄位,並脫敏指定欄位
  • 欄位的脫敏方式多樣
  • 欄位的脫敏方式可自定義

專案解決方案

1. 解決方案

使用註解方式,來支援對指定欄位,不同欄位,多種脫敏操作,並可以脫離物件。
使用工具物件,通過泛型傳參,來支援對不同物件的脫敏操作。

2. 實現程式碼

2.1 註解 Sensitive

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定義資料脫敏
 *
 * 例如: 身份證,手機號等資訊進行模糊處理
 *
 * @author lzddddd
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sensitive {

    /**
     * 脫敏資料型別
     */
    SensitiveType type() default SensitiveType.CUSTOMER;

    /**
     * 前置不需要打碼的長度
     */
    int prefixNoMaskLen() default 0;

    /**
     * 後置不需要打碼的長度
     */
    int suffixNoMaskLen() default 0;

    /**
     * 用什麼打碼
     */
    String symbol() default "*";

}

2.1 脫敏型別列舉 SensitiveType

public enum SensitiveType {

    /**
     * 自定義
     */
    CUSTOMER,
    /**
     * 名稱
     **/
    CHINESE_NAME,
    /**
     * 身份證證件號
     **/
    ID_CARD_NUM,
    /**
     * 手機號
     **/
    MOBILE_PHONE,
    /**
     * 固定電話
     */
    FIXED_PHONE,
    /**
     * 密碼
     **/
    PASSWORD,
    /**
     * 銀行卡號
     */
    BANKCARD,
    /**
     * 郵箱
     */
    EMAIL,
    /**
     * 地址
     */
    ADDRESS,

}

2.3 脫敏工具 DesensitizedUtils

import com.ruoyi.common.annotation.Sensitive;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.SensitiveType;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.*;

@Slf4j
public class DesensitizedUtils<T> {

    /**
     * 脫敏資料列表
     */
    private List<T> list;

    /**
     * 註解列表
     */
    private List<Object[]> fields;

    /**
     * 實體物件
     */
    public Class<T> clazz;


    public DesensitizedUtils(Class<T> clazz)
    {
        this.clazz = clazz;
    }

    /**
     * 初始化資料
     *
     * @param list 需要處理資料
     */
    public void init(List<T> list){
        if (list == null)
        {
            list = new ArrayList<T>();
        }
        this.list = list;

        // 得到所有定義欄位
        createSensitiveField();
    }

    /**
     * 初始化資料
     *
     * @param t 需要處理資料
     */
    public void init(T t){

        list = new ArrayList<T>();

        if (t != null)
        {
         list.add(t);
        }

        // 得到所有定義欄位
        createSensitiveField();
    }

    /**
     * 得到所有定義欄位
     */
    private void createSensitiveField()
    {
        this.fields = new ArrayList<Object[]>();
        List<Field> tempFields = new ArrayList<>();
        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        for (Field field : tempFields)
        {
            // 單註解
            if (field.isAnnotationPresent(Sensitive.class))
            {
                putToField(field, field.getAnnotation(Sensitive.class));
            }

            // 多註解
//            if (field.isAnnotationPresent(Excels.class))
//            {
//                Excels attrs = field.getAnnotation(Excels.class);
//                Excel[] excels = attrs.value();
//                for (Excel excel : excels)
//                {
//                    putToField(field, excel);
//                }
//            }
        }
    }

    /**
     * 對list資料來源將其裡面的資料進行脫敏處理
     *
     * @param list
     * @return 結果
     */
    public AjaxResult desensitizedList(List<T> list){

        if (list == null){
            return AjaxResult.error("脫敏資料為空");
        }

        // 初始化資料
        this.init(list);

        int failTimes = 0;

        for (T t: this.list) {
            if ((Integer)desensitization(t).get("code") != HttpStatus.SUCCESS){
                failTimes++;
            }
        }

        if (failTimes >0){
            return AjaxResult.error("脫敏操作中出現失敗",failTimes);
        }

        return AjaxResult.success();
    }

    /**
     * 放到欄位集合中
     */
    private void putToField(Field field, Sensitive attr)
    {
        if (attr != null)
        {
            this.fields.add(new Object[] { field, attr });
        }
    }

    /**
     * 脫敏:JavaBean模式脫敏
     *
     * @param t 需要脫敏的物件
     * @return
     */
    public AjaxResult desensitization(T t) {
        if (t == null){
            return AjaxResult.error("脫敏資料為空");
        }

        // 初始化資料
        init(t);

        try {
            // 遍歷處理需要進行 脫敏的欄位
            for (Object[] os : fields)
            {
                Field field = (Field) os[0];
                Sensitive sensitive = (Sensitive) os[1];
                // 設定實體類私有屬性可存取
                field.setAccessible(true);
                desensitizeField(sensitive,t,field);
            }
            return AjaxResult.success(t);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("紀錄檔脫敏處理失敗,回滾,詳細資訊:[{}]", e);
            return AjaxResult.error("脫敏處理失敗",e);
        }
    }

    /**
     * 對類的屬性進行脫敏
     *
     * @param attr 脫敏引數
     * @param vo 脫敏物件
     * @param field 脫敏屬性
     * @return
     */
    private void desensitizeField(Sensitive attr, T vo, Field field) throws IllegalAccessException {

        if (attr == null || vo == null || field == null){
            return ;
        }

        // 讀取物件中的屬性
        Object value = field.get(vo);
        SensitiveType sensitiveType = attr.type();
        int prefixNoMaskLen = attr.prefixNoMaskLen();
        int suffixNoMaskLen = attr.suffixNoMaskLen();
        String symbol = attr.symbol();

        //獲取屬性後現在預設處理的是String型別,其他型別資料可延伸
        Object val = convertByType(sensitiveType, value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        field.set(vo, val);
    }

    /**
     * 以類的屬性的get方法方法形式獲取值
     *
     * @param o 物件
     * @param name 屬性名
     * @return value
     * @throws Exception
     */
    private Object getValue(Object o, String name) throws Exception
    {
        if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name))
        {
            Class<?> clazz = o.getClass();
            Field field = clazz.getDeclaredField(name);
            field.setAccessible(true);
            o = field.get(o);
        }
        return o;
    }

    /**
     * 根據不同註解型別處理不同欄位
     */
    private Object convertByType(SensitiveType sensitiveType, Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) {
        switch (sensitiveType) {
            case CUSTOMER:
                value = customer(value, prefixNoMaskLen, suffixNoMaskLen, symbol);
                break;
            case CHINESE_NAME:
                value = chineseName(value, symbol);
                break;
            case ID_CARD_NUM:
                value = idCardNum(value, symbol);
                break;
            case MOBILE_PHONE:
                value = mobilePhone(value, symbol);
                break;
            case FIXED_PHONE:
                value = fixedPhone(value, symbol);
                break;
            case PASSWORD:
                value = password(value, symbol);
                break;
            case BANKCARD:
                value = bankCard(value, symbol);
                break;
            case EMAIL:
                value = email(value, symbol);
                break;
            case ADDRESS:
                value = address(value, symbol);
                break;
        }
        return value;
    }

    /*--------------------------下面的脫敏工具類也可以單獨對某一個欄位進行使用-------------------------*/

    /**
     * 【自定義】 根據設定進行設定
     *
     * @param value 需處理資料
     * @param symbol 填充字元
     * @return 脫敏後資料
     */
    public Object customer(Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) {

        //針對字串的處理
        if (value instanceof String){
            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }

        return value;
    }

    /**
     * 對字串進行脫敏處理
     *
     * @param s 需處理資料
     * @param prefixNoMaskLen 開頭展示字元長度
     * @param suffixNoMaskLen 結尾展示字元長度
     * @param symbol 填充字元
     * @return
     */
    private String handleString(String s, int prefixNoMaskLen, int suffixNoMaskLen, String symbol){
        // 是否為空
        if (StringUtils.isBlank(s)) {
            return "";
        }

        // 如果設定為空之類使用 * 代替
        if (StringUtils.isBlank(symbol)){
            symbol = "*";
        }

        // 對長度進行判斷
        int length = s.length();
        if (length > prefixNoMaskLen + suffixNoMaskLen){
            String namePrefix = StringUtils.left(s, prefixNoMaskLen);
            String nameSuffix = StringUtils.right(s, suffixNoMaskLen);
            s = StringUtils.rightPad(namePrefix, StringUtils.length(s) - suffixNoMaskLen, symbol).concat(nameSuffix);
        }

        return s;
    }

    /**
     * 【中文姓名】只顯示第一個漢字,其他隱藏為2個星號,比如:李**
     *
     * @param value 需處理資料
     * @param symbol 填充字元
     * @return 脫敏後資料
     */
    public String chineseName(Object value, String symbol) {

        //針對字串的處理
        if (value instanceof String){
            // 對前後長度進行設定 預設 開頭只展示一個字元
            int prefixNoMaskLen = 1;
            int suffixNoMaskLen = 0;

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";
    }

    /**
     * 【身份證號】顯示最後四位,其他隱藏。共計18位元或者15位,比如:*************1234
     *
     * @param value 需處理資料
     * @param symbol 填充字元
     * @return 脫敏後資料
     */
    public String idCardNum(Object value, String symbol) {

        //針對字串的處理
        if (value instanceof String){
            // 對前後長度進行設定 預設 結尾只展示四個字元
            int prefixNoMaskLen = 0;
            int suffixNoMaskLen = 4;

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";
    }

    /**
     * 【固定電話】 顯示後四位,其他隱藏,比如:*******3241
     *
     * @param value 需處理資料
     * @param symbol 填充字元
     * @return 脫敏後資料
     */
    public String fixedPhone(Object value, String symbol) {

        //針對字串的處理
        if (value instanceof String){
            // 對前後長度進行設定 預設 結尾只展示四個字元
            int prefixNoMaskLen = 0;
            int suffixNoMaskLen = 4;

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";

    }

    /**
     * 【手機號碼】前三位,後四位,其他隱藏,比如:135****6810
     *
     * @param value 需處理資料
     * @param symbol 填充字元
     * @return 脫敏後資料
     */
    public String mobilePhone(Object value, String symbol) {

        //針對字串的處理
        if (value instanceof String){
            // 對前後長度進行設定 預設 開頭只展示三個字元  結尾只展示四個字元
            int prefixNoMaskLen = 3;
            int suffixNoMaskLen = 4;

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";
    }

    /**
     * 【地址】只顯示到地區,不顯示詳細地址,比如:湖南省長沙市嶽麓區***
     *  只能處理 省市區的資料
     *
     * @param value 需處理資料
     * @param symbol 填充字元
     * @return
     */
    public String address(Object value, String symbol) {

        //針對字串的處理
        if (value instanceof String){
            // 對前後長度進行設定 預設 開頭只展示九個字元
            int prefixNoMaskLen = 9;
            int suffixNoMaskLen = 0;

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";
    }

    /**
     * 【電子郵箱】 郵箱字首僅顯示第一個字母,字首其他隱藏,用星號代替,@及後面的地址顯示,比如:d**@126.com
     *
     * @param value 需處理資料
     * @param symbol 填充字元
     * @return 脫敏後資料
     */
    public String email(Object value, String symbol) {

        //針對字串的處理
        if (value instanceof String){
            // 對前後長度進行設定 預設 開頭只展示一個字元  結尾只展示@及後面的地址
            int prefixNoMaskLen = 1;
            int suffixNoMaskLen = 4;

            String s = (String) value;
            if (StringUtils.isBlank(s)) {
                return "";
            }

            // 獲取最後一個@
            int lastIndex = StringUtils.lastIndexOf(s, "@");
            if (lastIndex <= 1) {
                return s;
            } else {
                suffixNoMaskLen = s.length() - lastIndex;
            }

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";

    }

    /**
     * 【銀行卡號】前六位,後四位,其他用星號隱藏每位1個星號,比如:6222600**********1234
     *
     * @param value 需處理資料
     * @param symbol 填充字元
     * @return 脫敏後資料
     */
    public String bankCard(Object value, String symbol) {

        //針對字串的處理
        if (value instanceof String){
            // 對前後長度進行設定 預設 開頭只展示六個字元  結尾只展示四個字元
            int prefixNoMaskLen = 6;
            int suffixNoMaskLen = 4;

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";

    }

    /**
     * 【密碼】密碼的全部字元都用*代替,比如:******
     *
     * @param value 需處理資料
     * @param symbol 填充字元
     * @return
     */
    public String password(Object value,String symbol) {

        //針對字串的處理
        if (value instanceof String){
            // 對前後長度進行設定 預設 開頭只展示六個字元  結尾只展示四個字元
            int prefixNoMaskLen = 0;
            int suffixNoMaskLen = 0;

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";
    }
}

3 使用範例

3.1 需註解物件

public class User {
    private static final long serialVersionUID = 1L;

    /** 普通使用者ID */
    private Long userId;

    /** 暱稱 */
    @Sensitive(type = SensitiveType.CUSTOMER,prefixNoMaskLen = 2,suffixNoMaskLen = 1)
    private String nickName;

    /** 姓名 */
    @Sensitive(type = SensitiveType.CHINESE_NAME)
    private String userName;

    /** 身份證 */
    @Sensitive(type = SensitiveType.ID_CARD_NUM)
    private String identityCard;

    /** 手機號碼 */
    @Sensitive(type = SensitiveType.MOBILE_PHONE)
    private String phoneNumber;
}

3.2 脫敏操作

		// 脫敏物件
		User user = new User();
		......
        DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class);
        desensitizedUtils.desensitization(user);

		//脫敏佇列
		List<User> users = new ArrayList<>();
		......
        DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class);
        desensitizedUtils.desensitizedList(users);

到此這篇關於SpringBoot專案中新增脫敏功能的文章就介紹到這了,更多相關SpringBoot脫敏內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com