jackson 轻松搞定接口数据脱敏

news2024/9/23 3:28:26

一、简介

实际的业务开发过程中,我们经常需要对用户的隐私数据进行脱敏处理,所谓脱敏处理其实就是将数据进行混淆隐藏,例如下图,将用户的手机号、地址等数据信息,采用*进行隐藏,以免泄露个人隐私信息。

图片

如果需要脱敏的数据范围很小很小,甚至就是指定的字段,一般的处理方式也很简单,就是写一个隐藏方法即可实现数据脱敏。

图片

如果是需求很少的情况下,采用这种方式实现没太大问题,好维护!

但如果是类似上面那种很多位置的数据,需要分门别类的进行脱敏处理,通过这种简单粗暴的处理,代码似乎就显得不太优雅了。

思考一下,我们可不可以在数据输出的阶段,进行统一数据脱敏处理,这样就可以省下不少体力活。

说到数据输出,很多同学可能会想到 JSON 序列化。是的没错,我们所熟悉的 web 系统,就是将数据通过 json 序列化之后展示给前端。

那么问题来了,如何在序列化的时候,进行数据脱敏处理呢?

废话不多说,代码直接撸上!

二、方案实践

2.1、首先添加依赖包

默认的情况下,如果当前项目已经添加了spring-web包或者spring-boot-starter-web包,因为这些jar包已经集成了jackson相关包,因此无需重复依赖。

如果当前项目没有jackson包,可以通过如下方式进行添加相关依赖包。

<!--jackson依赖-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.8</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.8</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>
2.2、编写脱敏类型枚举类,满足不同场景的处理
public enum SensitiveEnum {

    /**
     * 中文名
     */
    CHINESE_NAME,

    /**
     * 身份证号
     */
    ID_CARD,

    /**
     * 座机号
     */
    FIXED_PHONE,

    /**
     * 手机号
     */
    MOBILE_PHONE,

    /**
     * 地址
     */
    ADDRESS,

    /**
     * 电子邮件
     */
    EMAIL,

    /**
     * 银行卡
     */
    BANK_CARD,

    /**
     * 公司开户银行联号
     */
    CNAPS_CODE
}
2.3、编写脱敏注解类
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerialize.class)
public @interface SensitiveWrapped {

    /**
     * 脱敏类型
     * @return
     */
    SensitiveEnum value();
}
2.4、编写脱敏序列化类
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 java.io.IOException;
import java.util.Objects;

public class SensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {

    /**
     * 脱敏类型
     */
    private SensitiveEnum type;

  
    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        switch (this.type) {
            case CHINESE_NAME: {
                jsonGenerator.writeString(SensitiveInfoUtils.chineseName(s));
                break;
            }
            case ID_CARD: {
                jsonGenerator.writeString(SensitiveInfoUtils.idCardNum(s));
                break;
            }
            case FIXED_PHONE: {
                jsonGenerator.writeString(SensitiveInfoUtils.fixedPhone(s));
                break;
            }
            case MOBILE_PHONE: {
                jsonGenerator.writeString(SensitiveInfoUtils.mobilePhone(s));
                break;
            }
            case ADDRESS: {
                jsonGenerator.writeString(SensitiveInfoUtils.address(s, 4));
                break;
            }
            case EMAIL: {
                jsonGenerator.writeString(SensitiveInfoUtils.email(s));
                break;
            }
            case BANK_CARD: {
                jsonGenerator.writeString(SensitiveInfoUtils.bankCard(s));
                break;
            }
            case CNAPS_CODE: {
                jsonGenerator.writeString(SensitiveInfoUtils.cnapsCode(s));
                break;
            }
        }
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        // 为空直接跳过
        if (beanProperty != null) {
            // 非 String 类直接跳过
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
                SensitiveWrapped sensitiveWrapped = beanProperty.getAnnotation(SensitiveWrapped.class);
                if (sensitiveWrapped == null) {
                    sensitiveWrapped = beanProperty.getContextAnnotation(SensitiveWrapped.class);
                }
                if (sensitiveWrapped != null) {
                    // 如果能得到注解,就将注解的 value 传入 SensitiveSerialize
                    return new SensitiveSerialize(sensitiveWrapped.value());
                }
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(beanProperty);
    }

    public SensitiveSerialize() {}

    public SensitiveSerialize(final SensitiveEnum type) {
        this.type = type;
    }
}

其中createContextual的作用是通过字段已知的上下文信息定制JsonSerializer对象。

2.4、编写脱敏工具类
import org.apache.commons.lang3.StringUtils;

public class SensitiveInfoUtils {

    /**
     * [中文姓名] 只显示第一个汉字,其他隐藏为2个星号<例子:李**>
     */
    public static String chineseName(final String fullName) {
        if (StringUtils.isBlank(fullName)) {
            return "";
        }
        final String name = StringUtils.left(fullName, 1);
        return StringUtils.rightPad(name, StringUtils.length(fullName), "*");
    }

    /**
     * [中文姓名] 只显示第一个汉字,其他隐藏为2个星号<例子:李**>
     */
    public static String chineseName(final String familyName, final String givenName) {
        if (StringUtils.isBlank(familyName) || StringUtils.isBlank(givenName)) {
            return "";
        }
        return chineseName(familyName + givenName);
    }

    /**
     * [身份证号] 显示最后四位,其他隐藏。共计18位或者15位。<例子:420**********5762>
     */
    public static String idCardNum(final String id) {
        if (StringUtils.isBlank(id)) {
            return "";
        }

        return StringUtils.left(id, 3).concat(StringUtils
                .removeStart(StringUtils.leftPad(StringUtils.right(id, 4), StringUtils.length(id), "*"),
                        "***"));
    }

    /**
     * [固定电话] 后四位,其他隐藏<例子:****1234>
     */
    public static String fixedPhone(final String num) {
        if (StringUtils.isBlank(num)) {
            return "";
        }
        return StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*");
    }

    /**
     * [手机号码] 前三位,后四位,其他隐藏<例子:138******1234>
     */
    public static String mobilePhone(final String num) {
        if (StringUtils.isBlank(num)) {
            return "";
        }
        return StringUtils.left(num, 3).concat(StringUtils
                .removeStart(StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*"),
                        "***"));

    }

    /**
     * [地址] 只显示到地区,不显示详细地址;我们要对个人信息增强保护<例子:北京市海淀区****>
     *
     * @param sensitiveSize 敏感信息长度
     */
    public static String address(final String address, final int sensitiveSize) {
        if (StringUtils.isBlank(address)) {
            return "";
        }
        final int length = StringUtils.length(address);
        return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*");
    }

    /**
     * [电子邮箱] 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示<例子:g**@163.com>
     */
    public static String email(final String email) {
        if (StringUtils.isBlank(email)) {
            return "";
        }
        final int index = StringUtils.indexOf(email, "@");
        if (index <= 1) {
            return email;
        } else {
            return StringUtils.rightPad(StringUtils.left(email, 1), index, "*")
                    .concat(StringUtils.mid(email, index, StringUtils.length(email)));
        }
    }

    /**
     * [银行卡号] 前六位,后四位,其他用星号隐藏每位1个星号<例子:6222600**********1234>
     */
    public static String bankCard(final String cardNum) {
        if (StringUtils.isBlank(cardNum)) {
            return "";
        }
        return StringUtils.left(cardNum, 6).concat(StringUtils.removeStart(
                StringUtils.leftPad(StringUtils.right(cardNum, 4), StringUtils.length(cardNum), "*"),
                "******"));
    }

    /**
     * [公司开户银行联号] 公司开户银行联行号,显示前两位,其他用星号隐藏,每位1个星号<例子:12********>
     */
    public static String cnapsCode(final String code) {
        if (StringUtils.isBlank(code)) {
            return "";
        }
        return StringUtils.rightPad(StringUtils.left(code, 2), StringUtils.length(code), "*");
    }

}
2.5、编写测试实体类

最后,我们编写一个实体类UserEntity,看看转换后的效果如何?

public class UserEntity {

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 用户姓名
     */
    private String name;

    /**
     * 手机号
     */
    @SensitiveWrapped(SensitiveEnum.MOBILE_PHONE)
    private String mobile;

    /**
     * 身份证号码
     */
    @SensitiveWrapped(SensitiveEnum.ID_CARD)
    private String idCard;

    /**
     * 年龄
     */
    private String sex;

    /**
     * 性别
     */
    private int age;

    //省略get、set...
}

测试程序如下:

public class SensitiveDemo {

    public static void main(String[] args) throws JsonProcessingException {
        UserEntity userEntity = new UserEntity();
        userEntity.setUserId(1l);
        userEntity.setName("张三");
        userEntity.setMobile("18000000001");
        userEntity.setIdCard("420117200001011000008888");
        userEntity.setAge(20);
        userEntity.setSex("男");

        //通过jackson方式,将对象序列化成json字符串
        ObjectMapper objectMapper = new ObjectMapper();
        System.out.println(objectMapper.writeValueAsString(userEntity));
    }
}

结果如下:

{"userId":1,"name":"张三","mobile":"180****0001","idCard":"420*****************8888","sex":"男","age":20}

很清晰的看到,转换结果成功!

如果你当前的项目是基于SpringMVC框架进行开发的,那么在对象返回的时候,框架会自动帮你采用jackson框架进行序列化。

@RequestMapping("/hello")
public UserEntity hello() {
    UserEntity userEntity = new UserEntity();
    userEntity.setUserId(1l);
    userEntity.setName("张三");
    userEntity.setMobile("18000000001");
    userEntity.setIdCard("420117200001011000008888");
    userEntity.setAge(20);
    userEntity.setSex("男");
    return userEntity;
}

请求网页http://127.0.0.1:8080/hello,结果如下:

图片

三、小结

在实际的业务场景开发中,采用注解方式进行全局数据脱敏处理,可以有效的解决敏感数据隐私泄露的问题。

本文主要从实操层面对数据脱敏处理做了简单的介绍,可能有些网友还有更好的解决方案,欢迎下方留言,后面如果遇到了好的解决办法,也会分享给大家,愿对大家有所帮助!

四、参考

1.https://blog.csdn.net/liufei198613/article/details/79009015

2.https://yanbin.blog/customize-jackson-annotation-and-disable-specific-annotation/

3.https://www.jianshu.com/p/d54a970c251a

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

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

相关文章

C++_基本语法笔记_模板

函数模板 基本使用 思想是Java里的泛型&#xff08;不确定用什么类型的数据&#xff09; 这里template这一句&#xff0c;意思是声明T是泛型&#xff0c;后面写用到泛型T的函数。 建议都用template<class T> 应用场景&#xff1a;不同数据类型的交换函数 两种使用方法…

猫咪泪痕消除术!希喂主食罐开启补水大作战!

我家小猫之前的泪痕真的不是一般的严重&#xff0c;每天都能看到它眼角挂着两条明显的褐色痕迹&#xff0c;我每天早晚都得小心翼翼地帮它擦拭&#xff0c;但效果总是微乎其微&#xff0c;眼眶边总是留下那么一圈顽固的黑色痕迹&#xff0c;我真是既忧心又无奈。 后来&#xff…

2024【十大品牌网】发布|车载WiFi十大品牌排行榜

自驾游无法随时随地联网?货车司机流量不够用&#xff1f;网约车线上抢单网速不好&#xff1f;来看看最新发布的车载WiFi十大品牌排行榜&#xff0c;帮你解决网速慢、流量不够用的问题&#xff01; 1.格行 格行作为有15年历史的老牌物联网企业&#xff0c;在产品质量和服务上…

沃可趣助力乐园工会:员工活动的数字化创新与实践

企业组织员工活动难处多多 预算不足都算轻的&#xff0c;时间协调也颇有难度&#xff0c;活动内容设计更是既要激发员工兴趣&#xff0c;又要符合企业文化&#xff0c;这让原本专注于OA服务和福利平台开发的公司纷纷退缩。 因此&#xff0c;当群硕决定在沃可趣上增加活动管理…

渲染优化策略,如何在有限资源下实现最佳视觉效果

在数字内容创作领域&#xff0c;渲染是一个至关重要的环节&#xff0c;它决定了最终图像或动画的视觉质量。然而&#xff0c;高质量的渲染往往伴随着高计算资源需求&#xff0c;如何在有限资源下实现最佳的视觉效果&#xff0c;成为了创作者们不断探索的课题。 一、优化3D模型…

青颖飞帆(青书)大模型算法工程师面试题6道|含解析

问题1、Decoder-Only和Encoder-Decoder模型相比有什么优势&#xff1f;在训练和推理效率上有什么区别&#xff1f;**** Decoder-Only模型&#xff1a;结构较为简洁&#xff0c;通常只由一个解码器组成。模型参数较少&#xff0c;相比于Encoder-Decoder模型在训练和推理上可能更…

UE5学习笔记9-创建一个小窗口提示人物是否和武器重叠

一、目标 创建一个UsrWidget去显示如果人物和武器重叠显示窗口&#xff0c;如果人物和武器不重叠将窗口隐藏 二、创建窗口并显示 1.创建一个窗口蓝图类&#xff0c;命名为PickUpWidget&#xff0c;这个蓝图类不需要C类&#xff0c;在对应文件夹中单机右键选择用户界面的控件蓝…

torch分布式训练DataParallel和DistributedDataParallel

Pytorch 分布式训练主要有两种方式&#xff1a; torch.nn.DataParallel > 简称 DP torch.nn.parallel.DistributedDataParallel > 简称DDP 其中 DP 只用于单机多卡&#xff0c;DDP 可以用于单机多卡也可用于多机多卡&#xff0c;后者现在也是Pytorch训练的主流用法&…

维基知识库系统Wiki.js本地Linux环境部署并配置公网地址远程访问

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

所生成项目的处理器架构“MSIL”与引用“***”的处理器架构“x86”不匹配。

在c#工程里新建了一个类库&#xff0c;编译的场合出现以下警告&#xff1a;C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\amd64\Microsoft.Common.CurrentVersion.targets(2401,5): warning MSB3270: 所生成项目的处理器架构“MSIL”与引用“…

单链表leetcode刷题/下(C语言版)

目录 题目1&#xff1a;返回倒数第k个结点 题目2&#xff1a;回文链表 题目3&#xff1a;相交链表 题目1&#xff1a;返回倒数第k个结点 如图&#xff0c;该题使用到了快慢指针的思想。先定义两个指针&#xff0c;同时指向链表的首元结点&#xff0c;然后让fast指针向前先走…

秋招突击——8/15——知识补充——Socket通信

文章目录 引言正文基于TCP协议的Socket通信基于UDP协议的Socket通信服务端如何接收更多项目多进程多线程IO多路复用select轮询IO多路复用epoll事件通知 使用Socket实现同一个机器上的多线程通信服务端创建对应socket监听端口客户端发起对应的连接请求 总结 引言 上次面试腾讯的…

Qt篇——:-1: error: collect2.exe: error: ld returned 1 exit status错误

错误如图所示&#xff1a; 出现这个错误的时候我们点击下面的 [4 编译输出]&#xff0c;可以看到具体的编译报错信息&#xff0c;找到对应的错误进行解决即可。 这里举个例子&#xff0c;比如最常见的一种错误原因是&#xff1a;multiple definition of ......。这代表着你重复…

视频号小店升级微信小店,视频号直播电商将迎来新变化!

8月12日&#xff0c;腾讯发布《视频号小店升级为微信小店的公告》-意见征集通知&#xff0c;及其相关规则通知。 ▲ 图片来源&#xff1a;腾讯公告 公告称&#xff1a;腾讯计划自08月25日起&#xff0c;正式支持商家将视频号小店升级成微信小店。 微信小店将进一步简化商家入驻…

C++语言基础|循环结构

C语言基础|循环结构 循环1. for语句2. while循环3. do…while语句 循环 在程序中&#xff0c;常常需要重复地执行某些操作。C提供了3种循环语句&#xff1a;for语句、while语句和do-while语句。在循环语句中&#xff0c;重复执行的操作叫做循环体。循环体可以是单条语句、块语…

一把手在精益生产的实施中扮演着怎样的角色?

在探讨一把手在精益生产实施中的角色时&#xff0c;我们不得不深入剖析精益生产的核心要义及其在现代企业管理中的重要性。精益生产作为一种以提高效率、降低成本、增加价值为目标的生产管理方式&#xff0c;其核心理念包括价值导向、流动制造、消除浪费、持续改进等。在这个过…

ubuntu服务器部署的mysql本地连不上的问题

试过了网上的所有方法,都连不上,可以执行: SELECT user, host, plugin FROM mysql.user WHERE user root; 查一下:plungin这个连接插件是不是auth_socket, auth_socket是只能本地连接的插件,需要修改: ALTER USER root% IDENTIFIED WITH mysql_native_password BY your_pass…

攀登SEO巅峰:代理技术如何成为网站排名提升的秘密武器

在当今这个数字化时代&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;已成为企业和电商从业者在网站获取自然流量、提升品牌知名度和促进业务增长的关键策略之一。然而&#xff0c;随着搜索引擎算法的不断升级和市场竞争的日益激烈&#xff0c;传统的SEO手段已难以满足现…

【数据结构】二叉树(二)遍历

上篇已经了解对二叉树有了大概了解&#xff0c;本篇学习二叉树的前序、中序、后序及层序遍历的递归与非递归共7种遍历方法&#xff0c;快收藏吧~ 目录 1、前序遍历 递归方式&#xff1a; 迭代方式&#xff1a; 2、中序遍历 递归方式&#xff1a; 迭代方式&#xff1a; …

数字医学影像系统PACS源码,三甲以下医院都能满足,C#语言开发,C/S架构系统成熟稳定,支持二次开发项目使用。

数字医学影像系统&#xff08;RIS/PACS&#xff09;源码&#xff0c;三甲以下的医院都能满足。开发技术&#xff1a;C/S架构&#xff0c;C#开发语言&#xff0c;数据库服务器采用Oracle数据库。 PACS系统模块组成 &#xff1a; 工作站&#xff1a; 分诊工作站、超声工作站、放…