目錄
SpringBoot專案中新增脫敏功能
專案背景
專案需求描述
,來支援對指定字段,不同字段,多種脫敏操作,並可以脫離物件。
首頁 Java java教程 SpringBoot怎麼新增脫敏功能

SpringBoot怎麼新增脫敏功能

May 23, 2023 pm 09:04 PM
springboot

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 "";
    }
}
登入後複製
#2.3 去敏感工具DesensitizedUtils
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 使用實例######3.1 需要註解物件###
		// 脱敏对象
		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);
登入後複製
###3.2 去敏感操作###rrreee

以上是SpringBoot怎麼新增脫敏功能的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

Springboot怎麼整合Jasypt實現設定檔加密 Springboot怎麼整合Jasypt實現設定檔加密 Jun 01, 2023 am 08:55 AM

Jasypt介紹Jasypt是一個java庫,它允許開發員以最少的努力為他/她的專案添加基本的加密功能,並且不需要對加密工作原理有深入的了解用於單向和雙向加密的高安全性、基於標準的加密技術。加密密碼,文本,數字,二進位檔案...適合整合到基於Spring的應用程式中,開放API,用於任何JCE提供者...添加如下依賴:com.github.ulisesbocchiojasypt-spring-boot-starter2. 1.1Jasypt好處保護我們的系統安全,即使程式碼洩露,也可以保證資料來源的

SpringBoot怎麼整合Redisson實現延遲隊列 SpringBoot怎麼整合Redisson實現延遲隊列 May 30, 2023 pm 02:40 PM

使用場景1、下單成功,30分鐘未支付。支付超時,自動取消訂單2、訂單簽收,簽收後7天未進行評估。訂單超時未評價,系統預設好評3、下單成功,商家5分鐘未接單,訂單取消4、配送超時,推播簡訊提醒…對於延時比較長的場景、即時性不高的場景,我們可以採用任務調度的方式定時輪詢處理。如:xxl-job今天我們採

怎麼在SpringBoot中使用Redis實現分散式鎖 怎麼在SpringBoot中使用Redis實現分散式鎖 Jun 03, 2023 am 08:16 AM

一、Redis實現分散式鎖原理為什麼需要分散式鎖在聊分散式鎖之前,有必要先解釋一下,為什麼需要分散式鎖。與分散式鎖相對就的是單機鎖,我們在寫多執行緒程式時,避免同時操作一個共享變數產生資料問題,通常會使用一把鎖來互斥以保證共享變數的正確性,其使用範圍是在同一個進程中。如果換做是多個進程,需要同時操作一個共享資源,如何互斥?現在的業務應用通常是微服務架構,這也意味著一個應用會部署多個進程,多個進程如果需要修改MySQL中的同一行記錄,為了避免操作亂序導致髒數據,此時就需要引入分佈式鎖了。想要實現分

springboot讀取檔案打成jar包後存取不到怎麼解決 springboot讀取檔案打成jar包後存取不到怎麼解決 Jun 03, 2023 pm 04:38 PM

springboot讀取文件,打成jar包後訪問不到最新開發出現一種情況,springboot打成jar包後讀取不到文件,原因是打包之後,文件的虛擬路徑是無效的,只能通過流去讀取。文件在resources下publicvoidtest(){Listnames=newArrayList();InputStreamReaderread=null;try{ClassPathResourceresource=newClassPathResource("name.txt");Input

Springboot+Mybatis-plus不使用SQL語句進行多表新增怎麼實現 Springboot+Mybatis-plus不使用SQL語句進行多表新增怎麼實現 Jun 02, 2023 am 11:07 AM

在Springboot+Mybatis-plus不使用SQL語句進行多表添加操作我所遇到的問題準備工作在測試環境下模擬思維分解一下:創建出一個帶有參數的BrandDTO對像模擬對後台傳遞參數我所遇到的問題我們都知道,在我們使用Mybatis-plus中進行多表操作是極其困難的,如果你不使用Mybatis-plus-join這一類的工具,你只能去配置對應的Mapper.xml文件,配置又臭又長的ResultMap,然後再寫對應的sql語句,這種方法雖然看上去很麻煩,但具有很高的靈活性,可以讓我們

SpringBoot怎麼自訂Redis實作快取序列化 SpringBoot怎麼自訂Redis實作快取序列化 Jun 03, 2023 am 11:32 AM

1.自訂RedisTemplate1.1、RedisAPI預設序列化機制基於API的Redis快取實作是使用RedisTemplate範本進行資料快取操作的,這裡開啟RedisTemplate類,查看該類別的源碼資訊publicclassRedisTemplateextendsRedisAccessorimplementsRedisOperations,BeanClassLoaderAware{//聲明了value的各種序列化方式,初始值為空@NullableprivateRedisSe

SpringBoot與SpringMVC的比較及差別分析 SpringBoot與SpringMVC的比較及差別分析 Dec 29, 2023 am 11:02 AM

SpringBoot和SpringMVC都是Java開發中常用的框架,但它們之間有一些明顯的差異。本文將探究這兩個框架的特點和用途,並對它們的差異進行比較。首先,我們來了解一下SpringBoot。 SpringBoot是由Pivotal團隊開發的,它旨在簡化基於Spring框架的應用程式的建立和部署。它提供了一種快速、輕量級的方式來建立獨立的、可執行

springboot怎麼取得application.yml裡值 springboot怎麼取得application.yml裡值 Jun 03, 2023 pm 06:43 PM

在專案中,很多時候需要用到一些配置信息,這些信息在測試環境和生產環境下可能會有不同的配置,後面根據實際業務情況有可能還需要再做修改。我們不能將這些設定在程式碼中寫死,最好是寫到設定檔中,例如可以把這些資訊寫到application.yml檔案中。那麼,怎麼在程式碼裡取得或使用這個位址呢?有2個方法。方法一:我們可以透過@Value註解的${key}即可取得設定檔(application.yml)中和key對應的value值,這個方法適用於微服務比較少的情形方法二:在實際專案中,遇到業務繁瑣,邏

See all articles