【数据脱敏方案】不使用 AOP + 注解,使用 SpringBoot+YAML 实现

这篇具有很好参考价值的文章主要介绍了【数据脱敏方案】不使用 AOP + 注解,使用 SpringBoot+YAML 实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

引入

在项目中遇到一个需求,需要对交易接口返回结果中的指定字段进行脱敏操作,但又不能使用AOP+注解的形式,于是决定使用一种比较笨的方法:

  1. 首先将所有需要脱敏字段及其对应脱敏规则存储到 Map 中。
  2. 在接口返回时,遍历结果中的所有字段,判断字段名在 Map 中是否存在:
    • 如果不存在:说明该字段不需要脱敏,不做处理即可。
    • 如果存在:说明该字段需要脱敏,从 Map 中获取对应的脱敏规则进行脱敏。
  3. 最后返回脱敏之后的结果。

认识 YAML 格式规范

由于返回的结果涉及到嵌套 Map,所以决定采用 YAML 格式的文件存储脱敏规则,那么为了大家统一维护和开发,就需要大家对 YAML 格式进行了解,遵守规范,不易出错,少走弯路。

YAML(YAML Ain’t Markup Language)与传统的 JSON、XML 和 Properties 文件一样,都是用于数据序列化的格式,常用于配置文件和数据传输。

相比于其他格式,YAML 是一种轻量级的数据序列化格式,它的设计初衷是为了简化复杂性,提高人类可读性,并且易于实现和解析。

  • 与 JSON 相比:YAML 在语法上更为灵活,允许使用更简洁的方式来表示数据结构。

  • 与 XML 相比:YAML 的语法更为简洁,没有繁琐的标签和尖括号。

  • 与 Properties 相比:YAML 支持更复杂的数据结构,包括嵌套的键值对和列表。

除此之外,YAML 还支持跨平台、跨语言,可以被多种编程语言解析,这使得YAML非常适合用于不同语言之间的数据传输和交换。

YAML 文件的语法非常简洁明了,以下是它的语法规范:

  1. 基本语法:

    • 使用 缩进表示层级关系,可以使用空格或制表符进行缩进,但不能混用。
    • 使用冒号(:)表示键值对,键值对之间使用换行分隔。
    • 使用破折号(-)表示列表项,列表项之间也使用换行分隔。
    # 使用缩进表示层级关系
    server:
      port: 8080
    
    # 使用冒号表示键值对
    name: John Smith
    age: 30
    
    # 使用破折号表示列表项
    hobbies:
      - reading
      - hiking
      - swimming
    
  2. 注释:

    • 使用井号(#)表示注释,在 # 后面的内容被视为注释,可以出现在行首或行尾。
    # 这是一个注释
    name: John Smith
    age: 30 # 这也是一个注释
    
  3. 字符串:

    • 字符串可以使用单引号或双引号括起来,也可以不使用引号。
    • 使用双引号时,可以使用转义字符(如 \n 表示换行)和转义序列(如 \u 表示 Unicode 字符)。
    # 使用双引号表示字符串
    name: "John Smith"
    
    # 使用单引号表示字符串
    nickname: 'Johnny'
    
  4. 键值对:

    • 键值对使用冒号(:)表示,键和值之间使用一个 空格 分隔。
    • 键可以是字符串或纯量(如整数、布尔值等)。
    • 值可以是字符串、纯量、列表或嵌套的键值对。
    # 键和值之间使用一个空格分隔
    name: John Smith
    
    # 键可以是字符串或纯量
    age: 30
    
    # 值可以是字符串、纯量、列表或嵌套的键值对
    address:
      city: San Francisco
      state: California
      zip: 94107
    
  5. 列表:

    • 使用破折号(-)表示列表项。
    • 列表项可以是字符串、纯量或嵌套的列表或键值对。
    # 使用破折号表示列表项
    hobbies:
      - reading
      - hiking
      - swimming
      
    # 列表项可以是字符串、纯量或嵌套的列表或键值对
    people:
      - name: John Smith
        age: 30
      - name: Jane Doe
        age: 25
    
  6. 引用:

    • 使用&表示引用,使用*表示引用的内容。
    # 使用&表示引用
    address: &myaddress
      city: San Francisco
      state: California
      zip: 94107
      
    # 使用*表示引用的内容
    shippingAddress: *myaddress
    
  7. 多行文本块:

    • 使用|保留换行符,保留文本块的精确格式。
    • 使用>折叠换行符,将文本块折叠成一行,并根据内容自动换行。
    # 使用|保留换行符
    description: |
      This is a
      multi-line
      string.
      
    # 使用>折叠换行符
    summary: >
      This is a summary
      that may contain
      line breaks.
    
  8. 数据类型:

    • YAML支持多种数据类型,包括字符串、整数、浮点数、布尔值、日期和时间等。
    • 可以使用标记来表示一些特殊的数据类型,如 !!str 表示字符串类型、!!int 表示整数类型等。
    # 使用标记表示数据类型
    age: !!int 30
    weight: !!float 65.5
    isMale: !!bool true
    created: !!timestamp '2022-01-01 12:00:00'
    
  9. 多文件:

    • 可以使用—表示多个 YAML 文件之间的分隔符。每个文件可以使用任何 YAML 语法。
    # 第一个YAML文件
    name: John Smith
    age: 30
    
    ---
    
    # 第二个YAML文件
    hobbies:
      - reading
      - hiking
      - swimming
    

定义脱敏规则格式

对于数据结构简单的接口返回结果,脱敏规则格式定义为【交易号->字段->规则】:

交易号:
  字段名:
    规则: '/^(1[3-9][0-9])\d{4}(\d{4}$)/'

同时接口返回的结果中可能用有嵌套列表,那么针对这种复杂的结构就定义格式为【交易号->字段(列表)->字段->规则】,即:

交易号:
  字段名(列表):
    字段名:
      规则: '/^(1[3-9][0-9])\d{4}(\d{4}$)/'

使用这种层级结构,我们完全可以通过 Map.get("Key") 的形式获取到指定交易,指定字段的脱敏规则。

脱敏逻辑实现

读取 YAML 配置文件获取脱敏规则

  1. 首先创建 YAML 文件 desensitize.yml 添加对应交易字段的脱敏规则:

    Y3800:
      phone:
        rule: "(\\d{3})\\d{4}(\\d{4})"
        format: "$1****$2"
      idCard:
        rule: "(?<=\\w{6})\\w(?=\\w{4})"
        format: "*"
    Y3801:
      idCard:
        rule: "(?<=\\w{3})\\w(?=\\w{4})"
        format: "+"
      list:
        phone:
          rule: "(\\d{3})\\d{4}(\\d{4})"
          format: "$1++++$2"
    
  2. 定义脱敏工具类 DataDesensitizationUtils 编写我们的脱敏逻辑:

    public class DataDesensitizationUtils {
    }
    
  3. DataDesensitizationUtils 工具类中,我们需要实现在项目启动时,读取 desensitize.yml 文件中的内容,并转为我们想要的 Map 键值对数据类型:

    /**
     * 读取yaml文件内容并转为Map
     * @param yamlFile yaml文件路径
     * @return Map对象
     */
    public static Map<String, Object> loadYaml(String yamlFile) {
        Yaml yaml = new Yaml();
        try (InputStream in = DataDesensitizationUtils.class.getResourceAsStream(yamlFile)) {
            return yaml.loadAs(in, Map.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    

    在上述代码中,我们通过 getResourceAsStream 方法根据指定的 YAML 文件的路径从类路径中获取资源文件的输入流。

    然后使用 loadAs 方法将输入流中的内容按照 YAML 格式进行解析,并将解析结果转换为指定的 Map.class 类型。

    最后使用 try-with-resources 语句来自动关闭输入流。

通过键路径获取对应字段规则

原始
  1. 在上文中我们已经将 desensitize.yml 文件中所有的脱敏规则都以 key-Value 的形式存储到了 Map 中,因此我们只需要通过 Key 从 Map 中获取即可。接下来编写方法通过 Key 获取指定字段对应脱敏规则:

    public static void main(String[] args) {
        // 加载 YAML 文件并获取顶层的 Map 对象,路径基于 resources 目录
        Map<String, Object> yamlMap = loadYaml("/desensitize.yml");
        System.out.println(yamlMap);
    
        // 从顶层的 Map 中获取名为 "Y3800" 的嵌套 Map
        Map<String, Object> Y3800= (Map<String, Object>) yamlMap.get("Y3800");
        System.out.println(Y3800);
    
        // 从 "Y3800" 的嵌套 Map 中获取名为 "phone" 的嵌套 Map
        Map<String, Object> phone = (Map<String, Object>) Y3800.get("phone");
        System.out.println(phone);
    }
    

    输出结果如下:

    {Y3800={phone={rule=(\d{3})\d{4}(\d{4}), format=$1****$2}, idCard={rule=(?<=\w{3})\w(?=\w{4}), format=*}}, Y3801={name={rule=.(?=.), format=+}, idCard={rule=(?<=\w{3})\w(?=\w{4}), format=+}, list={card={rule=\d(?=\d{4}), format=+}}}}
    {phone={rule=(\d{3})\d{4}(\d{4}), format=$1****$2}, idCard={rule=(?<=\w{3})\w(?=\w{4}), format=*}}
    {rule=(\d{3})\d{4}(\d{4}), format=$1****$2}
    

    转为 JSON 格式显示如下:

    • 输出 YAML 文件中的全部数据:

      {
        "Y3800": {
          "phone": {
            "rule": "(\\d{3})\\d{4}(\\d{4})",
            "format": "$1****$2"
          },
          "idCard": {
            "rule": "(?<=\\w{3})\\w(?=\\w{4})",
            "format": "*"
          }
        },
        "Y3801": {
          "name": {
            "rule": ".(?=.)",
            "format": "+"
          },
          "idCard": {
            "rule": "(?<=\\w{3})\\w(?=\\w{4})",
            "format": "+"
          },
          "list": {
            "card": {
              "rule": "\\d(?=\\d{4})",
              "format": "+"
            }
          }
        }
      }
      
    • 输出 Y3800 层级下的数据:

      {
        "phone": {
          "rule": "(\\d{3})\\d{4}(\\d{4})",
          "format": "$1****$2"
        },
        "idCard": {
          "rule": "(?<=\\w{3})\\w(?=\\w{4})",
          "format": "*"
        }
      }
      
    • 输出 phone 层级下的数据:

      {
        "rule": "(\\d{3})\\d{4}(\\d{4})",
        "format": "$1****$2"
      }
      

在这里,我们需要仔细思考一下,在我们通过 Key 获取指定层级下的数据时,我们需要不断的调用 Map.get("Key") 方法,即结构每嵌套一次,就需要一次 getKey,那么这里是否有优化的方法呢?

答案是:有的,因为有问题就会有答案。

优化后

首先我们需要先了解一个概念:

Y3800:
  phone:
    rule: "(\\d{3})\\d{4}(\\d{4})"
    format: "$1****$2"

当我们要从上述数据中获取 phone 的脱敏规则时,我们需要先从 Map 中 get("Y3800") 获取 Y3800 下的数据,再通过 get("phone") 获取 phone 下的规则,那么 Y3800->phone 就是 phone 的键路径。

基于此,我们可以实现这样一个方法,我们直接给出指定字段的键路径,在方法中通过递归的方式从 Map 中获取到该键路径下的所有数据,然后返回即可。

即优化思路为:通过递归和判断来遍历嵌套的 Map,直到找到键路径所对应的最里层的嵌套 Map,并返回该 Map 对象。

优化后方法如下:

/**
 * 递归获取嵌套 Map 数据
 *
 * @param map  嵌套数据源的 Map
 * @param keys 嵌套键路径
 * @return 嵌套数据对应的 Map
 */
@SuppressWarnings("unchecked")
public static Map<String, Object> getNestedMapValues(Map<String, Object> map, String... keys) {
    // 如果键路径为空或者第一个键不在 Map 中,则返回 null
    if (keys.length == 0 || !map.containsKey(keys[0])) {
        return null;
    }

    // 获取第一个键对应的嵌套对象
    Object nestedObject = map.get(keys[0]);

    // 如果键路径长度为 1,说明已经到达最里层的嵌套 Map,直接返回该 Map 对象
    if (keys.length == 1) {
        if (nestedObject instanceof Map) {
            return (Map<String, Object>) nestedObject;
        } else {
            return null;
        }
    } else {
        // 如果嵌套对象是 Map,继续递归查找下一个键的嵌套 Map
        if (nestedObject instanceof Map) {
            return getNestedMapValues((Map<String, Object>) nestedObject, Arrays.copyOfRange(keys, 1, keys.length));
        } else {
            // 嵌套对象既不是 Map 也不是 List,返回 null
            return null;
        }
    }
}

调用方法时传入 Key 的嵌套路径即可:

public static void main(String[] args) {
    // 加载 YAML 文件并获取顶层的 Map 对象
    Map<String, Object> yamlMap = loadYaml("/desensitize.yml");
    System.out.println(yamlMap);

    // 获取 Y3800 -> phone 下的数据转为 Map
    Map<String, Object> y3800PhoneMap = YamlUtils.getNestedMap(yamlMap, "Y3800", "phone");
    System.out.println("Y3800 -> phone : " + y3800NameMap);
}

具体来说,主要分为以下几步:

  1. 首先判断键路径是否为空或者第一个键是否在 Map 中。如果键路径为空或者第一个键不在 Map 中,则返回 null。
  2. 获取第一个键对应的嵌套对象。通过 get 方法获取第一个键对应的嵌套对象。
  3. 判断是否到达最里层的嵌套 Map。如果键路径长度为 1,说明已经到达最里层的嵌套 Map,直接返回该 Map 对象。
  4. 继续递归查找下一个键的嵌套 Map。如果嵌套对象是 Map,则继续递归查找下一个键的嵌套 Map。
  5. 返回结果。返回递归查找的结果。

对数据进行脱敏处理

获取到字段的脱敏规则后,我们就可以编写方法实现对源数据做脱敏处理,脱敏方法如下:

/**
 * 使用指定规则对数据进行脱敏处理
 *
 * @param data 要进行脱敏处理的数据
 * @param map 包含脱敏规则和格式的参数映射
 *            - "rule" 表示脱敏规则的正则表达式
 *            - "format" 表示替换脱敏部分的字符串,默认为 "*"
 * @return 脱敏后的数据
 */
private static String desensitizeLogic(String data, Map<String, Object> map) {
    if (map.containsKey("rule")) {
        String rule = (String) map.get("rule");
        String sign = "*";
        if (map.containsKey("format")) {
            sign = (String) map.get("format");
        }
        return data.replaceAll(rule, sign);
    }
    return data;
}

递归生成字段对应的键路径

目前我们已经实现了通过字段的键路径获取到该字段对应规则的方法 getNestedMapValues(),那么接下来我们只需要生成字段对应的键路径,然后调用方法 getNestedMapValues() 获取到脱敏规则后调用 desensitizeLogic() 对源数据进行脱敏即可。

提供源数据格式如下:

{
  "txEntity": {
    "idCard": "130428197001180384",
    "name": "赵士杰",
    "list": [
      {
        "phone": "17631007015"
      },
      {
        "phone": "17631007015"
      }
    ]
  },
  "txHeader": {
    "servNo": "Y3801"
  }
}

根据上述数据结构,首先我们需要从 txHeader 中获取 servNo,之后递归遍历 txEntity 中的元素即可。

具体方法如下:

/**
 * 对指定实体数据进行脱敏处理
 *
 * @param entity 要进行脱敏处理的实体数据
 * @param servNo 当前交易的服务号,用于记录日志
 * @param path 当前实体数据在整个数据结构中的路径,用于记录日志
 */
public static void parseData(Object entity, String servNo, String path) {
    if (entity instanceof Map) {
        for (Map.Entry<String, Object> entry : ((Map<String, Object>) entity).entrySet()) {
            // 计算当前键值对在整个数据结构中的路径
            String currentPath = path.isEmpty() ? entry.getKey() : path + "," + entry.getKey();
            if (entry.getValue() instanceof Map) {
                // 如果当前值是 Map 类型,则递归处理子节点
                parseData(entry.getValue(), servNo, currentPath);
            } else if (entry.getValue() instanceof List) {
                // 如果当前值是 List 类型,则遍历列表中的每个元素并递归处理子节点
                for (Object item : (List) entry.getValue()) {
                    if (item instanceof Map) {
                        parseData(item, servNo, currentPath);
                    }
                }
            } else {
                // 如果当前值不是 Map 或 List,则进行脱敏处理
                String p = servNo + "," +currentPath;
                String[] keyPaths = p.split(",");

                // 获取当前节点的脱敏规则和格式
                Map<String, Object> nestedMap = getNestedMap(keyPaths);

                if(Objects.nonNull(nestedMap)){
                    // 记录日志
                    log.info("-----------------交易【{}】,字段【{}】开始脱敏-----------------",servNo,currentPath.replace(",","->"));
                    log.info("原始值:【{}:{}】",entry.getKey(),entry.getValue());
                    log.info("脱敏规则:{}",nestedMap);
                    // 对当前节点的值进行脱敏处理
                    String desensitized = desensitizeLogic((String) entry.getValue(), nestedMap);
                    entry.setValue(desensitized);
                    // 记录日志
                    log.info("脱敏值:【{}:{}】",entry.getKey(),entry.getValue());
                    log.info("-----------------交易【{}】,字段【{}】脱敏结束-----------------",servNo,currentPath.replace(",","->"));
                }
            }
        }
    }
}

该方法接收一个实体数据 entity,一个服务号 servNo 和一个路径 path 作为参数。在方法体内,会遍历实体数据的键值对,并根据具体情况递归处理子节点或进行脱敏处理。

  • 当实体数据的值为 Map 类型时,方法会递归处理子节点;
  • 当值为 List 类型时,方法会遍历列表中的每个元素并递归处理子节点;
  • 当值既不是 Map 也不是 List 时,方法会根据服务号和路径获取脱敏规则,并对当前节点的值进行脱敏处理,并记录脱敏日志。

脱敏处理的具体逻辑和规则通过调用 getNestedMap 方法和 desensitizeLogic 方法来实现,其中 getNestedMap 方法用于获取脱敏规则,desensitizeLogic 方法用于根据脱敏规则对数据进行脱敏处理。

注:请注意本文中提供的数据样例的层次结构是和 YAML 中定义的结构是一样的,再通过上述方法递归后生成的键路径是和从 YAML 中获取规则所需的键路径是一致的,因此可以直接调用 getNestedMapValues() 获取脱敏规则。在实际使用中,其他数据结构需要重写该逻辑。

脱敏测试

编写 Main 方法调用:

public class Demo {

    public static Map<String, Object> getData() {

        HashMap<String, Object> phone = new HashMap<>();
        phone.put("phone", "17631007015");

        HashMap<String, Object> phone2 = new HashMap<>();
        phone2.put("phone", "17631007015");

        List<HashMap<String, Object>> list = new ArrayList<>();
        list.add(phone);
        list.add(phone2);

        HashMap<String, Object> txEntity = new HashMap<>();
        txEntity.put("name", "赵士杰");
        txEntity.put("idCard", "130428197001180384");
        txEntity.put("list", list);

        HashMap<String, Object> result = new HashMap<>();

        result.put("txEntity", txEntity);

        HashMap<String, Object> txHeader = new HashMap<>();
        txHeader.put("servNo", "Y3801");
        result.put("txHeader", txHeader);

        return result;
    }

    public static void main(String[] args) {

        Map<String, Object> data = getData();
        
        // 假设data中包含接口返回的数据
        if (data.containsKey("txHeader") && data.get("txHeader") instanceof Map) {
            String servNo = ((Map<String, String>) data.get("txHeader")).get("servNo");
            DataDesensitizationUtils.parseData(data.get("txEntity"), servNo, "");
        }
        
    }

}

运行测试,控制台输出如下:

-----------------交易【Y3801】,字段【idCard】开始脱敏-----------------
原始值:【idCard:130428197001180384】
脱敏规则:{rule=(?<=\w{3})\w(?=\w{4}), format=+}
脱敏值:【idCard:130+++++++++++0384】
-----------------交易【Y3801】,字段【idCard】脱敏结束-----------------
-----------------交易【Y3801】,字段【list->phone】开始脱敏-----------------
原始值:【phone:17631007015】
脱敏规则:{rule=(\d{3})\d{4}(\d{4}), format=$1++++$2}
脱敏值:【phone:176++++7015】
-----------------交易【Y3801】,字段【list->phone】脱敏结束-----------------
-----------------交易【Y3801】,字段【list->phone】开始脱敏-----------------
原始值:【phone:17631007015】
脱敏规则:{rule=(\d{3})\d{4}(\d{4}), format=$1++++$2}
脱敏值:【phone:176++++7015】
-----------------交易【Y3801】,字段【list->phone】脱敏结束-----------------

数据脱敏后如下:

{
  "txEntity": {
    "idCard": "130+++++++++++0384",
    "name": "赵士杰",
    "list": [
      {
        "phone": "176++++7015"
      },
      {
        "phone": "176++++7015"
      }
    ]
  },
  "txHeader": {
    "servNo": "Y3801"
  }
}

完整工具类

封装成完整的工具类如下:文章来源地址https://www.toymoban.com/news/detail-840406.html

/**
 * @ClassName DataDesensitizationUtils
 * @Description 数据脱敏工具类
 * @Author 赵士杰
 * @Date 2024/1/25 20:15
 */
@Slf4j
@SuppressWarnings("unchecked")
public class DataDesensitizationUtils {

    // YAML 文件路径
    private static final String YAML_FILE_PATH = "/tuomin.yml";

    // 存储解析后的 YAML 数据
    private static Map<String, Object> map;

    static {
        // 创建 Yaml 对象
        Yaml yaml = new Yaml();
        // 通过 getResourceAsStream 获取 YAML 文件的输入流
        try (InputStream in = DataDesensitizationUtils.class.getResourceAsStream(YAML_FILE_PATH)) {
            // 解析 YAML 文件为 Map 对象
            map = yaml.loadAs(in, Map.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取嵌套的 Map 数据
     *
     * @param keys 嵌套键路径
     * @return 嵌套数据对应的 Map
     */
    private static Map<String, Object> getNestedMap(String... keys) {
        return getNestedMapValues(map, keys);
    }

    /**
     * 递归获取嵌套 Map 数据
     *
     * @param map  嵌套数据源的 Map
     * @param keys 嵌套键路径
     * @return 嵌套数据对应的 Map
     */
    private static Map<String, Object> getNestedMapValues(Map<String, Object> map, String... keys) {
        // 如果键路径为空或者第一个键不在 Map 中,则返回 null
        if (keys.length == 0 || !map.containsKey(keys[0])) {
            return null;
        }

        // 获取第一个键对应的嵌套对象
        Object nestedObject = map.get(keys[0]);

        // 如果键路径长度为 1,说明已经到达最里层的嵌套 Map,直接返回该 Map 对象
        if (keys.length == 1) {
            if (nestedObject instanceof Map) {
                return (Map<String, Object>) nestedObject;
            } else {
                return null;
            }
        } else {
            // 如果嵌套对象是 Map,继续递归查找下一个键的嵌套 Map
            if (nestedObject instanceof Map) {
                return getNestedMapValues((Map<String, Object>) nestedObject, Arrays.copyOfRange(keys, 1, keys.length));
            } else {
                // 嵌套对象既不是 Map 也不是 List,返回 null
                return null;
            }
        }
    }

    /**
     * 对指定实体数据进行脱敏处理
     *
     * @param entity 要进行脱敏处理的实体数据
     * @param servNo 当前交易的服务号,用于记录日志
     * @param path 当前实体数据在整个数据结构中的路径,用于记录日志
     */
    public static void parseData(Object entity, String servNo, String path) {
        if (entity instanceof Map) {
            for (Map.Entry<String, Object> entry : ((Map<String, Object>) entity).entrySet()) {
                String currentPath = path.isEmpty() ? entry.getKey() : path + "," + entry.getKey();
                if (entry.getValue() instanceof Map) {
                    parseData(entry.getValue(), servNo, currentPath);
                } else if (entry.getValue() instanceof List) {
                    for (Object item : (List) entry.getValue()) {
                        if (item instanceof Map) {
                            parseData(item, servNo, currentPath);
                        }
                    }
                } else {
                    String p = servNo + "," + currentPath;
                    String[] keyPaths = p.split(",");

                    Map<String, Object> nestedMap = getNestedMap(keyPaths);

                    if (Objects.nonNull(nestedMap)) {
                        log.info("-----------------交易【{}】,字段【{}】开始脱敏-----------------", servNo, currentPath.replace(",", "->"));
                        log.info("原始值:【{}:{}】", entry.getKey(), entry.getValue());
                        log.info("脱敏规则:{}", nestedMap);
                        String desensitized = desensitizeLogic((String) entry.getValue(), nestedMap);
                        entry.setValue(desensitized);
                        log.info("脱敏值:【{}:{}】", entry.getKey(), entry.getValue());
                        log.info("-----------------交易【{}】,字段【{}】脱敏结束-----------------", servNo, currentPath.replace(",", "->"));
                    }

                }
            }
        }
    }

    /**
     * 脱敏逻辑
     * @param data 源数据
     * @param map 脱敏规则
     * @return 脱敏后的数据
     */
    private static String desensitizeLogic(String data, Map<String, Object> map) {
        if (map.containsKey("rule")) {
            String rule = (String) map.get("rule");
            String sign = "*";
            if (map.containsKey("format")) {
                sign = (String) map.get("format");
            }
            return data.replaceAll(rule, sign);
        }
        return data;
    }

}

到了这里,关于【数据脱敏方案】不使用 AOP + 注解,使用 SpringBoot+YAML 实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【SpringBoot应用篇】【AOP+注解】SpringBoot+SpEL表达式基于注解实现权限控制

    Spring 表达式语言 SpEL 是一种非常强大的表达式语言,它支持在运行时查询和操作对象图。 它提供了许多高级功能,例如方法调用和基本的字符串模板功能。 表达式语言给静态Java语言增加了动态功能。 Spring 表达式语言最初是为 Spring 社区创建的,它拥有一种受良好支持的表

    2024年02月20日
    浏览(45)
  • 【SpringBoot】AOP 自定义注解的使用详解

            Spring 中的切面 Aspect,这是 Spring 的一大优势。面向切面编程往往让我们的开发更加低耦合,也大大减少了代码量,同时呢让我们更专注于业务模块的开发,把那些与业务无关的东西提取出去,便于后期的维护和迭代。         AOP 的全称为 Aspect Oriented Programming,

    2024年02月05日
    浏览(48)
  • springboot自定义注解+aop+redis实现延时双删

    redis作为用的非常多的缓存数据库,在多线程场景下,可能会出现数据库与redis数据不一致的现象 数据不一致的现象:https://blog.csdn.net/m0_73700925/article/details/133447466 这里采用aop+redis来解决这个方法: 删除缓存 更新数据库 延时一定时间,比如500ms 删除缓存 这里之所以要延时一

    2024年01月17日
    浏览(42)
  • spring boot 使用AOP+自定义注解+反射实现操作日志记录修改前数据和修改后对比数据,并保存至日志表

    使用FieldMeta自定义注解,看个人业务自行觉得是否需要重新定义实体 实现类 :通过该实现类获取更新前后的数据。 该实现类的实现原理为:获取入参出入的id值,获取sqlSessionFactory,通过sqlSessionFactory获取selectByPrimaryKey()该方法,执行该方法可获取id对应数据更新操作前后的数

    2024年01月23日
    浏览(52)
  • Java 实现数据脱敏的技术方案

    数据脱敏是保护个人隐私的一种重要手段,它通过对敏感信息进行处理,将敏感信息转换为不敏感的信息,以保护个人隐私不被泄漏。在Java中,数据脱敏也是一项非常重要的技术,本文将从数据脱敏的概念、Java中的数据脱敏原理、Java中的数据脱敏方法以及如何实现数据脱敏

    2024年02月08日
    浏览(56)
  • SpringBoot实现返回值数据脱敏

    介绍 SpringBoot实现返回数据脱敏 有时,敏感数据返回时,需要进行隐藏处理,但是如果一个字段一个字段的进行硬编码处理的话,不仅增加了工作量,而且后期需求变动的时候,更加是地狱般的工作量变更。 下面,通过身份证,姓名,密码,手机号等等示例去演示脱敏的流程

    2024年02月15日
    浏览(37)
  • SpringBoot自定义注解+AOP+redis实现防接口幂等性重复提交,从概念到实战

    本文为千锋教育技术团独家创作,更多技术类知识干货,点个关注持续追更~ 接口幂等性是Web开发中非常重要的一个概念,它可以保证多次调用同一个接口不会对结果产生影响。如果你想了解更多关于接口幂等性的知识,那么本文就是一个不错的起点。 在Web开发中,我们经常

    2024年02月03日
    浏览(56)
  • 【Spring】使用自定义注解方式实现AOP鉴权

    AOP,是一种面向切面编程,可以通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 在软件开发中,鉴权(Authentication)是一项非常重要的安全措施,用于验证用户身份和权限。在应用程序中,我们通常会使用AOP(Aspect-Oriented Programming)来实现鉴权功能

    2024年02月11日
    浏览(47)
  • SpringBoot中优雅的实现隐私数据脱敏(提供Gitee源码)

    前言:在实际项目开发中,可能会对一些用户的隐私信息进行脱敏操作,传统的方式很多都是用replace方法进行手动替换,这样会由很多冗余的代码并且后续也不好维护,本期就讲解一下如何在SpringBoot中优雅的通过序列化的方式去实现数据的脱敏操作! 目录 一、导入pom依赖

    2024年02月12日
    浏览(40)
  • SpringBoot利用自定义json序列化器实现敏感字段数据脱敏

    物料准备: 1.hutool依赖 2.自定义的jackson序列化器 3.测试@JsonSerialize效果 因为案例代码用到了hutool提供的DesensitizedUtil数据脱敏工具类,这里要引入hutool的依赖。 如果你需要自定义 数据脱敏的逻辑,可以不引入这个依赖 自定义一个手机号脱敏序列化器 自定义一个邮箱脱敏序列化

    2024年02月12日
    浏览(68)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包