实现 SpringBoot 项目中的隐私数据脱敏

news2025/1/11 0:30:32

实现 SpringBoot 项目中的隐私数据脱敏

  • 什么是数据脱敏
  • 如何实现数据脱敏
  • 注解使用demo

什么是数据脱敏

为了避免“用户信息泄露”的风险项(就是后台系统里用户的一些隐私数据直接明文显示了),其实指的就是要做数据脱敏。数据脱敏:把系统里的一些敏感数据进行加密处理后再返回,达到保护隐私作用。

如何实现数据脱敏

拿到数据后在序列化的时候再进行脱敏(如用 fastjson、jackson),以下是选择使用选用 jackson 的序列化来实现。

  • 创建隐私数据类型枚举:PrivacyTypeEnum,分为常用的姓名、身份证号、邮箱、手机号、自定义。
package com.cbex.partyconstruction.privacyencrypt;

import lombok.Getter;

/**
 * 隐私数据类型枚举
 */
@Getter
public enum PrivacyTypeEnum {
    /** 自定义(此项需设置脱敏的范围),也可以设置脱敏使用的字符*/
    CUSTOMER,

    /** 姓名 */
    NAME,

    /** 身份证号 */
    ID_CARD,

    /** 手机号 */
    PHONE,

    /** 邮箱 */
    EMAIL,
}

  • 创建自定义隐私注解:PrivacyEncrypt,注解定义了参数包括脱敏的数据类型、前置不需要打码的长度、后置不需要打码的长度、用什么打码,当脱敏数据类型为自定义时可以设置需要打码的长度和打码使用的字符。
package com.cbex.partyconstruction.privacyencrypt;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

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

/**
 * 自定义数据脱敏注解
 */
@Target(ElementType.FIELD) // 作用在字段上
@Retention(RetentionPolicy.RUNTIME) // class文件中保留,运行时也保留,能通过反射读取到
@JacksonAnnotationsInside // 表示自定义自己的注解PrivacyEncrypt
@JsonSerialize(using = PrivacySerializer.class) // 该注解使用序列化的方式
public @interface PrivacyEncrypt {

    /**
     * 脱敏数据类型(没给默认值,所以使用时必须指定type)
     */
    PrivacyTypeEnum type();

    /**
     * 前置不需要打码的长度
     */
    int prefixNoMaskLen() default 1;

    /**
     * 后置不需要打码的长度
     */
    int suffixNoMaskLen() default 1;

    /**
     * 用什么打码
     */
    String symbol() default "*";
}

  • 创建自定义序列化器:PrivacySerializer
    1、这里是具体的实现过程,因为要脱敏的数据都是 String 类型的,所以继承 JsonSerializer 时的类型填 String。
    2、重写的 serialize 方法是实现脱敏的核心,根据类型 type 的不同去设置序列化后的值
    3、重写的 createContextual 方法就是去读取我们自定义的 PrivacyEncrypt 注解,打造一个上下文的环境。
package com.cbex.partyconstruction.privacyencrypt;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.util.Objects;

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

    // 脱敏类型
    private PrivacyTypeEnum privacyTypeEnum;
    // 前几位不脱敏
    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 != privacyTypeEnum) {
            switch (privacyTypeEnum) {
                case CUSTOMER:
                    jsonGenerator.writeString(PrivacyUtil.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, symbol));
                    break;
                case NAME:
                    jsonGenerator.writeString(PrivacyUtil.hideChineseName(origin));
                    break;
                case ID_CARD:
                    jsonGenerator.writeString(PrivacyUtil.hideIDCard(origin));
                    break;
                case PHONE:
                    jsonGenerator.writeString(PrivacyUtil.hidePhone(origin));
                    break;
                case EMAIL:
                    jsonGenerator.writeString(PrivacyUtil.hideEmail(origin));
                    break;
                default:
                    throw new IllegalArgumentException("unknown privacy type enum " + privacyTypeEnum);
            }
        }
    }

    @Override
    public JsonSerializer<?> createContextual(final SerializerProvider serializerProvider,
                                              final BeanProperty beanProperty) throws JsonMappingException {
        if (beanProperty != null) {
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
                PrivacyEncrypt privacyEncrypt = beanProperty.getAnnotation(PrivacyEncrypt.class);
                if (privacyEncrypt == null) {
                    privacyEncrypt = beanProperty.getContextAnnotation(PrivacyEncrypt.class);
                }
                if (privacyEncrypt != null) {
                    return new PrivacySerializer(privacyEncrypt.type(), privacyEncrypt.prefixNoMaskLen(),
                            privacyEncrypt.suffixNoMaskLen(), privacyEncrypt.symbol());
                }
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(null);
    }
}

  • 隐私数据隐藏工具类:PrivacyUtil
package com.cbex.partyconstruction.privacyencrypt;

public class PrivacyUtil {

    /**
     * 隐藏手机号中间四位
     */
    public static String hidePhone(String phone) {
        return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    }

    /**
     * 隐藏邮箱
     */
    public static String hideEmail(String email) {
        return email.replaceAll("(\\w?)(\\w+)(\\w)(@\\w+\\.[a-z]+(\\.[a-z]+)?)", "$1****$3$4");
    }

    /**
     * 隐藏身份证
     */
    public static String hideIDCard(String idCard) {
        return idCard.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1*****$2");
    }

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

//    /**
//     * 【身份证号】显示前4位, 后2位
//     */
//    public static String hideIdCard(String idCard) {
//        return desValue(idCard, 4, 2, "*");
//    }

//    /**
//     * 【手机号码】前三位,后四位,其他隐藏。
//     */
//    public static String hidePhone(String phone) {
//        return desValue(phone, 3, 4, "*");
//    }

    /**
     * 对字符串进行脱敏操作
     * @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();
    }


}

这个工具类其实可以自己定,根据自己的业务去扩展

在自定义注解 PrivacyEncrypt 里,只有 type 的值为 PrivacyTypeEnum.CUSTOMER(自定义)时,才需要指定脱敏范围,即 prefixNoMaskLen 和 suffixNoMaskLen 的值,像邮箱、手机号这种隐藏格式都采用固定的

注解使用demo

1、使用固定类型的脱敏方式

public class StaffInfoPo {
    @ApiModelProperty(value = "主键")
    private Long id;

    @ApiModelProperty(value = "职工姓名")
    private String name;

    @ApiModelProperty(value = "职工手机号")
    @FieldEncrypt
    @PrivacyEncrypt(type = PrivacyTypeEnum.PHONE)
    private String phone;

}

在这里插入图片描述

2、使用自定义的脱敏方式

public class StaffInfoPo {
    @ApiModelProperty(value = "主键")
    private Long id;

    @ApiModelProperty(value = "职工姓名")
    private String name;

    @ApiModelProperty(value = "职工手机号")
    @PrivacyEncrypt(type = PrivacyTypeEnum.CUSTOMER,symbol = "?",prefixNoMaskLen = 2,suffixNoMaskLen = 2)
    private String phone;
    
}

在这里插入图片描述

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

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

相关文章

谷歌浏览器自带的翻译功能无法使用的解决办法

谷歌浏览器自带的翻译功能无法使用的解决办法 到 C:\Windows\System32\drivers\etc 目录找到hosts文件用记事本或者notepad打开在文件末尾添加&#xff1a;142.250.4.90 translate.googleapis.com要有管理员的身份才可以对hosts文件进行修改修改完毕以后&#xff0c;打开cmd命令…

MySQL 如何查找删除重复行

如何查找重复行 第一步是定义什么样的行才是重复行。多数情况下很简单&#xff1a;它们某一列具有相同的值。本文采用这一定义&#xff0c;或许你对“重复”的定义比这复杂&#xff0c;你需要对sql做些修改。本文要用到的数据样本&#xff1a; create table test(id int not …

解决Vue使用UEditor百度编辑器,上传图片服务配置问题

前言 查看文档前先查看http://fex.baidu.com/ueditor/#server-jsp 理解手册基本配置 此文档只针对TomcatWeb服务 vue版本为2.0版本和 vue-cli2.0 环境配置 需求配置实际项目配置JDK 1.6java version “19.0.1” 2022-10-18Apache Tomcat 6.0Server version: Apache Tomcat…

狂神说笔记——Nginx快速入门28

Nginx快速入门 在低并发的情况下&#xff0c;一个jar包启动应用就够了&#xff0c;然后内部tomcat返回内容给用户。 随着用户越来越多了&#xff0c;并发量慢慢增大了&#xff0c;此时一台服务器满足不了需求了。 于是进行横向扩展&#xff0c;又增加了服务器。这个时候几个项目…

VTK-路径规划

前言&#xff1a;本博文主要研究VTK中路径规划相关的内容&#xff0c;后期会持续更新路径规划相关的拓展应用&#xff0c;希望能给各位小伙伴一些帮助&#xff0c;也希望小伙伴们多多关注支持。 vtkGraphGeodesicPath 位置&#xff1a;..\Filter\Modeling 描述&#xff1a;路…

DDS 发送大数据

Sending Large DataThis section describes the capabilities offered by Connext DDS—specifically, RTI FlatData™ language binding and Zero Copy transfer over shared memory—that allow sending and receiving large data samples with minimum latency. In this sec…

Oracle 19c - 手动升级到 Non-CDB Oracle Database 19c 的完整核对清单 (Doc ID 2577572.1)

Oracle 19c - 手动升级到 Non-CDB Oracle Database 19c 的完整核对清单 (Doc ID 2577572.1)正在上传…重新上传取消To Bottom 文档内容 用途适用范围详细信息关于新的 Autoupgrade utility步骤 1: 升级到数据库 19c 的升级路径能够直接升级到 Oracle 19c 的数据库最小版本以下…

mongodb-18.聚合查询练习1

文章目录bulk writeaddFields增加field嵌套增加field覆盖显示用变量替换向数组中增加元素分组 bucket并行执行多个bucket$bucketAuto$count$document$facet1.使用Aggregation对象实现2.使用Aggregates实现$graphLookup 文档递归查询跨多文档递归$graphLookupbulk write db.piz…

第四十二讲:神州防火墙路由模式的初始配置

防火墙作为局域网的智能网关&#xff0c;处于内网和外网之间&#xff0c;必须工作在路由模式。路由模式下&#xff0c;防火墙上添加默认路由&#xff0c;配置SNAT转换&#xff0c;隐藏私有地址&#xff0c;内部用户正常访问外网。从安全考虑&#xff0c;内网处于trust区域&…

《2022年度ASA广告表现报告》生成,探索买量新高度!

回首 2022 年&#xff0c;ASA 广告的历程可以用“变化莫测”来形容&#xff0c;CPP 取代创意集、更新《广告指南》、上线新广告位等等&#xff0c;而这一系列改变&#xff0c;都在一定程度上影响着 ASA 广告的投放。一起来看看 2022 年度全球 ASA 广告的投放情况吧&#xff01;…

日常使用的WhatsApp如何防止被封?

最近好多做外贸的朋友反映&#xff0c;自己手机号码注册的WhatsApp账号被封了&#xff0c;该如何将解封。首先我们先要了解为什么会被封&#xff1f;被封肯定是因为违反了WhatsApp条款和条件&#xff0c;但是具体如何违反的呢&#xff1f;我们一起来看看你没有这样做过&#xf…

【express】中间件

中间件&#xff08;Middleware&#xff09;&#xff0c;特指业务流程的中间处理环节 1、调用流程 当一个请求到达Express的服务器之后&#xff0c;可以连续调用多个中间件&#xff0c;从而对这次请求进行预处理。 2、格式 Express的中间件&#xff0c;本质上就是一个functio…

repeat语句 及 赋值语句说明---verilog HDL

参考&#xff1a;verilog数字系统设计教程【第四版】夏宇闻 repeat语句用阻塞赋值语句&#xff0c;与用非阻塞语句产生的结果差别非常大&#xff0c;所以将二者放在同一篇文章中。 1、赋值语句 2、repeat 语句介绍   2.1、用法要点   2.2、代码举例    代码1&#xff1a;…

2023年北向L2接口的发展会怎么样?

众所周知北向L2接口的逐笔成交功能可以精确查看每笔成交&#xff0c;跟踪北向资金动向&#xff0c;那么由于北向资金动向是股市行情的晴雨表&#xff0c;因此股民做股票投资是要时刻关注着北向资金流动方向的&#xff0c;那么北向L2接口作为帮助头者提供跟踪资金动向的服务软件…

浅谈撮合引擎

浅谈撮合引擎设计撮合引擎简介撮合引擎的发展币安中小型交易所小型交易所业务交易流程竞价方式交易所常用指令开发简易架构设计撮数据结构设计交易委托账本限价委托单其它委托单关键代码实现1.创建一个ringbuffer2. 设置事件监听4.订单撮合主逻辑撮合分支processMath函数逻辑PS…

uniapp实现iOS支付苹果内购支付踩过的坑以及具体操作步骤

由于我们app会员属于虚拟产品&#xff0c;所以苹果商店要求我们必须选择苹果内购&#xff0c;否则就勒令下架。 无奈&#xff0c;于是就又开始了踩坑之旅~ uniapp可以直接使用uni-pay的插件去进行苹果内购。 但是&#xff0c;在对接自己的项目之前&#xff0c;建议先跑通示例项…

JavaEE-Spring(Spring中的五大类注解,@Bean注解,对象装配(@Autowired,@Resource),Bean对象在Spring中的作用域)

文章目录1. 配置扫描路径2. Spring五大类注解3. Spring Bean注解对象装配4. Bean对象在Spring中的作用域5. Bean生命周期1. 配置扫描路径 只有设置了扫描路径&#xff0c;其他的路径下注解不会被Spring扫描 这里设置路径为com.beans下 <?xml version"1.0" enc…

(七)devops持续集成开发——jenkins流水线发布一个node环境下的前端vue项目

前言 在前面的章节中已经介绍了jenkins集成前端流水化部署环境的内容&#xff0c;本节内容是关于前端项目的流水化部署发布&#xff0c;通过实操发布一个前端项目&#xff0c;从而完成前端项目的流水化发布。前端项目主要是静态资源的发布&#xff0c;这里我们以一个vue项目为…

智慧物流信息化供应链管理体系转型发展现状

现如今&#xff0c;伴随着时代的迅速发展和高新科技水准的持续提升&#xff0c;人们慢慢进入了信息时代。在其中&#xff0c;物流制造行业也从以往20年前的粗放型管理机制慢慢变化为信息化、智慧化的管理机制。 5G、云计算技术、AI、物联网等新技术的出现加快了各个领域经营方法…

k线图中的三条线是什么?

新手投资朋友可能会在行情软件中发现&#xff0c;图表中除了K线以外&#xff0c;其下方还有三条颜色不一样的曲线&#xff0c;到底这三条线有什么功能呢&#xff1f;它们的使用方法又是怎样的呢&#xff1f; 其实&#xff0c;这三条线分别是短、中、长周期移动平均线&#xff0…