一个注解(优雅)搞定SpringBoot项目中的身份证号、手机号等敏感数据脱敏

news2024/11/26 8:35:46

📞 文章简介:一个注解搞定身份证号、手机号等敏感数据脱敏处理
💡 创作目的:公司中CRM的第一版快封版了,基本功能都落实了。接下来就是预防等保了,我负责的是核心业务客户模块这里自然有很多数据要做脱敏。最开始想着service层进行数据处理,但是又繁琐又不够优雅。于是想到了使用注解搭配序列化做一个自动的脱敏处理!!!
📝 每期一言:只要勤奋学习,每天进步一点点,总有一天会成为飞过沧海横过大洋的海鸥。
☀️ 今日天气:2023-01-16 晴 前两天的风吹在脸上都疼 今天的阳光照在脸上很舒服 🧐

点在哪里跳哪里

  • 🪜第一格(简述)
    • 数据脱敏
    • 脱敏效果展示
  • 🪜第二格(设计)
    • 方案一(数据库处理)
    • 方案二(序列化处理)
  • 🪜第三格(编码)
    • 脱敏工具类:SensitivityUtil
    • 脱敏类型枚举:SensitivityTypeEnum
    • 脱敏注解: SensitivityEncrypt
    • 自定义脱敏序列化:SensitivitySerializer
  • 🪜第四格(测试)
    • 实体类接口测试
  • 🪜第五格(总结)

🪜第一格(简述)

数据脱敏

简单来说就是对一些敏感的数据进行隐藏,
比如说身份证号、手机号、姓名、邮箱、地址等这些不希望被工作人员或者有一些科技手段的人进行爬取利用。

脱敏效果展示

数据脱敏前
在这里插入图片描述

数据脱敏后
在这里插入图片描述
在这里插入图片描述

🪜第二格(设计)

因为等保现在对这个要求很严格,所以这些数据防护是必要的。
最开始想到的是在service层拿到数据后进行数据整理。
但是感觉冗余代码很多,就想了想别的办法。

方案一(数据库处理)

  • 在进行数据库存储的时候将这些敏感数据进行脱敏以后加入到数据库中(在执行添加操作的时候,数据库insert的时候)
    • 因为后期会有总表的存在,老板要看这些数据,还会有签署合同、生成在线合同的步骤,还是需要一些敏感数据的。所以这个方法不可取 ❌

方案二(序列化处理)

  • 返回数据的时候每个实体类都实现了序列化,所以可以在返回数据的时候在序列化的时候进行脱敏 ✅

🪜第三格(编码)

设计已经完成,采用序列化+注解的方式进行数据脱敏。
下面我们一起Let's coding

脱敏工具类:SensitivityUtil

public class SensitivityUtil {
    /**
     * 【中文姓名】只显示第一个汉字,其他隐藏为星号,比如:才**
     */
    public static String hideChineseName(String chineseName) {
        if (chineseName == null) {
            return null;
        }
        return desValue(chineseName, 1, 0, "*");
    }

     /**
     * 隐藏邮箱
     */
    public static String hideEmail(String email) {
        return email.replaceAll("(\\w?)(\\w+)(\\w)(@\\w+\\.[a-z]+(\\.[a-z]+)?)", "$1****$3$4");
         }
    /**
     * 隐藏手机号中间四位
     */
    public static String hidePhone(String phone) {
        return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    }
 
    /**
     * 隐藏身份证
     */
    public static String hideIDCard(String idCard) {
        return idCard.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1*****$2");
    }
 
    /**
     * 对字符串进行脱敏操作
     * @param origin          原始字符串
     * @param prefixNoMaskLen 左侧需要保留几位明文字段
     * @param suffixNoMaskLen 右侧需要保留几位明文字段
     * @param maskStr         用于遮罩的字符串, 如'*'
     * @return 脱敏后结果
     */
    public static String desValue(String origin, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) {
        if (origin == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0, n = origin.length(); i < n; i++) {
            if (i < prefixNoMaskLen) {
                sb.append(origin.charAt(i));
                continue;
            }
            if (i > (n - suffixNoMaskLen - 1)) {
                sb.append(origin.charAt(i));
                continue;
            }
            sb.append(maskStr);
        }
        return sb.toString();
    }

我这边的工具类是使用正则替换进行脱敏操作,这个工具类相对注解没有必须一致的要求,根据自己项目有的或者自己写的就可以

脱敏类型枚举:SensitivityTypeEnum


import lombok.Getter;
 
/**
 * 脱敏类型枚举
 */
@Getter
public enum SensitivityTypeEnum {

  /**
   * 姓名
   */
  NAME,
 
  /**
   * 身份证号
   */
  ID_CARD,

  /**
   * 邮箱
   */
  EMAIL,
 
  /**
   * 手机号
   */
  PHONE,

  /**
   *  自定义(此项需设置脱敏的前置后置长度)
   */
  CUSTOMER,
}

这个枚举主要用于在使用注解的时候指定是什么类型的脱敏方式,后续的序列化中也会根据使用的枚举进行对应的脱敏操作

脱敏注解: SensitivityEncrypt

 
/**
 * 自定义数据脱敏注解
 */
// 作用在字段上
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitivitySerializer.class) // 该注解使用序列化的方式
public @interface SensitivityEncrypt {
 
    /**
     * 脱敏数据类型(必须指定类型)
     */
    SensitivityTypeEnum type();
 
    /**
     * 前面有多少不需要脱敏的长度
     */
    int prefixNoMaskLen() default 1;
 
    /**
     * 后面有多少不需要脱敏的长度
     */
    int suffixNoMaskLen() default 1;
 
    /**
     * 用什么符号进行打码
     */
    String symbol() default "*";
}

注意:
这里用了jackson 要引入对应依赖

         <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.12.6.1</version>
        </dependency>

@JsonSerialize 注解使用自定义的序列化进行数据处理
参数的话需要指定基础脱敏的数据类型、脱敏数据保留的位置及自定义脱敏符号

自定义脱敏序列化:SensitivitySerializer


@NoArgsConstructor
@AllArgsConstructor
public class SensitivitySerializer extends JsonSerializer<String> implements ContextualSerializer {

  // 脱敏类型
  private SensitivityTypeEnum sensitivityTypeEnum;
  // 前几位不脱敏
  private Integer prefixNoMaskLen;
  // 最后几位不脱敏
  private Integer suffixNoMaskLen;
  // 用什么打码
  private String symbol;

  /**
   * 序列化 数据处理
   */
  @Override
  public void serialize(final String origin, final JsonGenerator jsonGenerator,
      final SerializerProvider serializerProvider) throws IOException {
    //判断是否为空脱敏类型
    if (StringUtils.isNotBlank(origin) && null != sensitivityTypeEnum) {
      //判断脱敏类型,进入对应类型的数据处理
      switch (sensitivityTypeEnum) {
        case CUSTOMER:
          jsonGenerator.writeString(SensitivityUtil.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, symbol));
          break;
        case NAME:
          jsonGenerator.writeString(SensitivityUtil.hideChineseName(origin));
          break;
        case ID_CARD:
          jsonGenerator.writeString(SensitivityUtil.hideIDCard(origin));
          break;
        case PHONE:
          jsonGenerator.writeString(SensitivityUtil.hidePhone(origin));
          break;
        case EMAIL:
          jsonGenerator.writeString(SensitivityUtil.hideEmail(origin));
          break;
        default:
          throw new IllegalArgumentException("unknown privacy type enum " + sensitivityTypeEnum);
      }
    } else {
      //如果脱敏类型为空则赋值空,要不然会导致序列化错误
      jsonGenerator.writeString("");
    }
  }

  /**
   * 读取自定义注解SensitivityEncrypt 创建上下文所需
   */
  @Override
  public JsonSerializer<?> createContextual(final SerializerProvider serializerProvider,
      final BeanProperty beanProperty) throws JsonMappingException {
    if (beanProperty != null) {
      if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
        SensitivityEncrypt sensitivityEncrypt = beanProperty.getAnnotation(SensitivityEncrypt.class);
        if (sensitivityEncrypt == null) {
          sensitivityEncrypt = beanProperty.getContextAnnotation(SensitivityEncrypt.class);
        }
        if (sensitivityEncrypt != null) {
          return new SensitivitySerializer(sensitivityEncrypt.type(), sensitivityEncrypt.prefixNoMaskLen(),
              sensitivityEncrypt.suffixNoMaskLen(), sensitivityEncrypt.symbol());
        }
      }
      return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
    }
    return serializerProvider.findNullValueSerializer(null);
  }
}
  • 这是最核心的序列化实现流程,继承了JsonSerializer并限制类型为String,固定参数类型
  • 通过重写createContextual用来读取SensitivityEncrypt注解的值,打造上下文环境所需
  • 重写的serialize的方法是整个数据脱敏的流程,通过判断什么类型从而对数据进行什么样的脱敏效果

🪜第四格(测试)

实体类接口测试

在对应的实体类中添加注解,调用接口进行测试

public class SysUser extends BaseEntity
{
    private static final long serialVersionUID = 1L;
    /** 用户账号 */
    private String userName;

    /** 用户昵称 */
    private String nickName;

    /** 用户邮箱 */
    @SensitivityEncrypt(type = SensitivityTypeEnum.EMAIL)
    private String email;

    /** 手机号码 */
    @SensitivityEncrypt(type = SensitivityTypeEnum.PHONE)
    private String phonenumber;

    /** 用户性别 */
    private String sex;

    /** 用户头像 */
    private String avatar;
}

在这里插入图片描述

脱敏大功告成啦!!!😁
具体的数据类型可以使用枚举,也可以使用自定义的脱敏类型

🪜第五格(总结)

数据安全现在已经是非常重要的一个问题,很多评级都会把数据安全放在前面,因为网上的大神很多,这其中的利益也很大,所以我们要时刻注意项目中数据的安全性

做为开发,虽然还没有达到顶级水平,但是在平时的开发中要多设计,避免重复造轮子才会提升更多。有很多可以使用起来的东西,比如说还可以使用Spring中的AOP进行后置数据处理。当然这些也可以前端做处理,但是考虑到数据的安全性,以及技术上的提升当然要我来做💁

🌈欢迎大佬们阅读,也希望可以有更好的想法提出。即使成功远在天边,道路崎岖多变,我也要勇往直前,不畏艰难。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/167309.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【十 三】Netty 私有协议栈开发

Netty 私有协议栈开发私有协议介绍跨节点通信私有协议栈设计私有协议栈的网络拓扑图协议栈功能描述协议的通信模型消息体定义消息定义表私有协议消息头定义私有协议支持的字段类型私有协议的编解码规范私有协议的编码私有协议的解码链路的建立客户端握手请求握手请求消息定义服…

智能上线阻断的算法和实践

一、背景 上线是引起系统故障的主要原因之一&#xff0c;在有些公司甚至能占到故障原因的一半&#xff0c;及时地发现并阻断、回滚存在故障隐患的上线&#xff0c;可以有效地减少系统故障。 事实上&#xff0c;现在很多平台已经提供了基于手工配置规则来进行变更阻断的能力。…

Modern C++ | 谈谈万能引用以及它的衍生问题:将亡值、引用折叠和完美转发

文章目录前言左右值引用的铺垫万能引用&&引用折叠完美转发前言 在学习Linux系统编程的过程中&#xff0c;想着得到了新知识&#xff0c;不能把旧知识忘了啊&#xff0c;所以我就读起了以前写的博客&#xff0c;在Modern C介绍这篇博客中&#xff0c;关于完美转发只是介…

安科瑞智能仪表在密集母线行业中的应用

安科瑞 华楠AMB100智能母线监控系列系统示意图功能1.电压、电流、频率、有功功率、无功功率、功率因数、有功电能、无功电能、2-63次谐波、温度、湿度、漏电流等电参量测量&#xff1b;2.交流2DI、2DO&#xff0c;直流4DI、2DO&#xff1b;3.可信号取电或者独立辅助电源供电&am…

windows安装VMware最新版本(VMware Workstation 17.0 Pro)详细教程

目录 一、概述 二、下载 VMware Workstation 17.0 Pro 三、安装 VMware Workstation 17.0 Pro 四、创建一个空的虚拟机 一、概述 VMware Workstation Pro™ 是一个运行在window或Linux系统的软件&#xff0c;使开发人员能够在同一台 PC 上同时运行多个基于 x86 的 Windows、Li…

国内最全的Spring Boot系列之六

在新的一年祝大家兔年大吉&#xff0c;兔耳冲天&#xff0c;动如脱兔! 2022年就这么过去了&#xff0c;闭上眼回首2022年发生的事情&#xff0c;犹如过眼云烟 —— 一事无成的感觉。 2022年到底都发生了什么事情&#xff0c;坚持了什么&#xff1f;于是我闭上眼睛&#xff0c…

2023-1-16 刷题情况

子相似性 III 题目描述 一个句子是由一些单词与它们之间的单个空格组成&#xff0c;且句子的开头和结尾没有多余空格。比方说&#xff0c;“Hello World” &#xff0c;“HELLO” &#xff0c;“hello world hello world” 都是句子。每个单词都 只 包含大写和小写英文字母。…

K8s 如何通过 ConfigMap 来配置 Redis ?

1、创建 ConfigMap YAML 配置文件 cat <<EOF >./example-redis-config.yaml apiVersion: v1 kind: ConfigMap metadata:name: example-redis-config data:redis-config: "" EOF2、创建 ConfigMap 资源 kubectl apply -f example-redis-config.yaml创建完成…

VMware 已将该虚拟机配置为使用 64 位客户机操作系统。但是,无法执行 64 位操作的解决方法

在电脑上安装VMWare&#xff0c;运行虚拟机发现提示无法执行64位操作。本人系统是windows10,64位系统。错误提示&#xff1a; 已将该虚拟机配置为使用 64 位客户机操作系统。但是&#xff0c;无法执行 64 位操作。 此主机支持 Intel VT-x&#xff0c;但 Intel VT-x 处于禁用状态…

HTML的body元素

&#xff08;1&#xff09;HTML的body元素 body是一个简单的HTML稳定最基本的必需元素。 <body> 标签定义文档的主体。 <body> 元素包含文档的所有内容&#xff08;比如文本、超链接、图像、表格和列表等等&#xff09;。 &#xff08;2&#xff09;HTML 网页结…

HTML零基础教程,九大知识点带你玩转前端(上)

博主&#xff1a;冰小九&#xff0c;新人博主一只&#xff0c;欢迎大佬前来指导 冰小九的主页喜欢请给个三连加关注呀&#xff0c;谢谢&#x1f337;&#x1f337;&#x1f337;三连加关注&#xff0c;追文不迷路&#xff0c;你们的支持就是我最大的动力&#xff01;&#xff0…

【自学Docker 】Docker管理命令大全(上)

文章目录Docker create命令Docker create命令概述Docker create命令语法Docker create命令参数列表案例创建容器运行容器Docker create命令总结Docker exec命令Docker exec命令概述Docker exec命令语法Docker exec命令参数列表案例查看文件创建文件进入容器Docker exec命令总结…

Dubbo 自适应SPI

Dubbo 自适应SPI 1. 原理 在 Dubbo 中&#xff0c;很多拓展都是通过 SPI 机制进行加载的&#xff0c;比如 Protocol、Cluster、LoadBalance 等。有时&#xff0c;有些拓展并不想在框架启动阶段被加载&#xff0c;而是希望在拓展方法被调用时&#xff0c;根据运行时参数进行加…

录屏软件无水印免费,分享一款功能强大且免费的录屏软件

市面上多数录屏软件&#xff0c;只能试用版录制几分钟的视频&#xff0c;且带有水印。想要长时间录制电脑屏幕、录制无水印的录屏&#xff0c;需要解锁才可以。那有没有一款录屏软件试用版就能无水印&#xff1f;当然有啦。小编今天给大家分享一款不限制录制时长&#xff0c;且…

springboot整合Freemarker模板引擎

2.2 模板引擎 2.2.1 什么是模板引擎 根据前边的数据模型分析&#xff0c;课程预览就是把课程的相关信息进行整合&#xff0c;在课程预览界面进行展示&#xff0c;课程预览界面与课程发布的课程详情界面一致&#xff0c;保证了教学机构人员发布前看到什么样&#xff0c;发布后…

【Win11 + VSCode配置OpenCV C++一站式开发调试环境教程】

Win11 VSCode配置OpenCV C一站式开发调试环境教程1 下载1.1 版本介绍&#xff1a;1.2 对应三个软件的连接&#xff1a;2 环境配置3 编译1 下载 需要下载三个软件&#xff1a;OpenCV 、MInGW、CMake 1.1 版本介绍&#xff1a; 打开 OpenCV-MinGW-Build&#xff1a;OpenCV-4.…

Android启动流程源码分析(基于Android S)

从上图我们可以清楚的看到Android系统的启动分为以下几个步骤 启动电源以及启动系统 当我们按下电源键时, 引导芯片代码开始从预定义的地方(固化在ROM)开始执行, 加载引导程序到RAM, 然后执行 引导程序 引导程序是在Android操作系统开始运行前的一个小程序. 引导程序是运行的…

图片转PDF怎么弄?这几个方法值得你试一试

PDF是一种特殊的文件格式&#xff0c;它可以在任何设备和平台上进行传输&#xff0c;并且能够保证文件版式不被修改&#xff0c;此外&#xff0c;还可以兼容不同的系统&#xff0c;因为它的这些优势&#xff0c;大多数的人就喜欢将自己编辑好的WORD、PPT、EXCEL、图片等文件转换…

MySQL InnoDB的MVCC实现机制

MySQL InnoDB的MVCC实现机制1.MVCC概述2.MVCC的实现原理隐式字段undo日志Read View(读视图)RR隔离级别的Read View方案1.MVCC概述 什么是MVCC&#xff1f; MVCC&#xff0c;即多版本并发控制。MVCC是一种并发控制的方法&#xff0c;一般在数据库管理系统中&#xff0c;实现对…

YOLOV8——快速训练指南(上手教程、自定义数据训练)

概述 本篇主要用于说明如何使用自己的训练数据&#xff0c;快速在YOLOV8 框架上进行训练。当前&#xff08;20230116&#xff09;官方文档和网上的资源主要都是在开源的数据集上进行测试&#xff0c;对于算法“小白”或者“老鸟”如何快速应用到自己的项目中&#xff0c;这…