数据脱敏处理

#哪个编程工具让你的工作效率翻倍?#

有关于数据脱敏处理,小编也是在文章上面看到的,感觉很有意思,那么,便深入研究了一下,首先我们先来看一下数据脱敏之后的结果吧?

用结果说话更能深入人心!!

下面是数据库中的字段:请看Phone属性!数据库中存储的就是用户直接输入的数据

但是,当我们对该phone字段脱敏处理以后,在前台我们看到的数据是:

这就能够有效的避免一些不必要的信息外泄!比如:银行卡?身份证号?地址?手机号码?等涉及到个人隐私信息!!真的很有用呢!!

那么,我们便来看一下实际的代码吧!!

首先添加依赖包

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

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

     <!-- Jackson Databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.10.1</version>
        </dependency>


        <!-- Apache Commons Lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

编写脱敏类型枚举类,满足不同场景的处理

package com.example.desensitization;

public enum SensitiveEnum {

    /**
     * 中文名
     */
    CHINESE_NAME,

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

    /**
     * 座机号
     */
    FIXED_PHONE,

    /**
     * 手机号
     */
    MOBILE_PHONE,

    /**
     * 地址
     */
    ADDRESS,

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

    /**
     * 银行卡
     */
    BANK_CARD,

    /**
     * 公司开户银行联号
     */
    CNAPS_CODE
}

编写脱敏注解类

package com.example.desensitization;

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();
}

编写脱敏序列化类

package com.example.desensitization;

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;

/**
 * 用于序列化字符串的脱敏处理类
 * 继承自JsonSerializer<String>,同时实现ContextualSerializer接口
 * 该类根据敏感信息类型对字符串进行脱敏处理
 */
public class SensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {

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


    /**
     * 根据脱敏类型对字符串进行脱敏处理
     *
     * @param s 输入的字符串
     * @param jsonGenerator 用于写入处理后的字符串的Json生成器
     * @param serializerProvider 序列化提供者
     * @throws IOException 如果写入操作导致IO错误
     */
    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        // 根据不同的敏感信息类型,调用相应的方法对敏感信息进行处理,并写入JSON
        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: {
                // 处理并写入地址敏感信息,精度为4级
                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: {
                // 处理并写入银行间清算系统(CNAPS)代码敏感信息
                jsonGenerator.writeString(SensitiveInfoUtils.cnapsCode(s));
                break;
            }
        }

    }

    /**
     * 创建上下文相关的序列化器
     *
     * @param serializerProvider 序列化提供者
     * @param beanProperty Bean属性
     * @return 创建的JsonSerializer
     * @throws JsonMappingException 如果出现映射异常
     */
    @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() {}

    /**
     * 带脱敏类型的构造函数
     *
     * @param type 脱敏类型
     */
    public SensitiveSerialize(final SensitiveEnum type) {
        this.type = type;
    }
}

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

编写脱敏工具类

package com.example.desensitization;

import org.apache.commons.lang3.StringUtils;

public class SensitiveInfoUtils {

    /**
     * 将中文全名中的第一个汉字显示出来,其余部分用两个星号隐藏。
     * 例如:李**。
     *
     * @param fullName 中文全名
     * @return 格式化后的姓名,如果输入为空白,则返回空字符串
     */
    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个星号。
     * 例如:李**。
     *
     * @param familyName 姓氏
     * @param givenName 名字
     * @return 格式化后的姓名,如果输入为空白,则返回空字符串
     */
    public static String chineseName(final String familyName, final String givenName) {
        if (StringUtils.isBlank(familyName) || StringUtils.isBlank(givenName)) {
            return "";
        }
        return chineseName(familyName + givenName);
    }


    /**
     * 对身份证号进行脱敏处理。
     * 身份证号的前三位和后四位保持不变,中间部分用星号替换。
     * 例如:4201**********5762
     *
     * @param id 身份证号码字符串,可以是15位或18位
     * @return 脱敏后的身份证号码字符串 如果输入为空或者空白字符串,则返回空字符串
     */
    public static String idCardNum(final String id) {
        // 如果身份证号为空或空白字符串,则直接返回空字符串
        if (StringUtils.isBlank(id)) {
            return "";
        }

        // 拼接字符串,前三位加星号填充加后四位
        // StringUtils.left(id, 3) 取字符串的前三位
        // StringUtils.right(id, 4) 取字符串的后四位
        // StringUtils.leftPad 在字符串左侧填充星号,直到达到原字符串长度
        // StringUtils.removeStart 移除左侧开始的三个星号,避免星号覆盖前三位身份证号
        return StringUtils.left(id, 3).concat(StringUtils
                .removeStart(StringUtils.leftPad(StringUtils.right(id, 4), StringUtils.length(id), "*"),
                        "***"));
    }

    /**
     * [固定电话] 后四位,其他隐藏<例子:****1234>
     *
     * @param num 电话号码字符串
     * @return 格式化后的电话号码字符串
     */
    public static String fixedPhone(final String num) {
        if (StringUtils.isBlank(num)) {
            return "";
        }
        // 除了最后四位号码外,其他位数用星号替换
        return StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*");
    }

    /**
     * [手机号码] 前三位,后四位,其他隐藏<例子:138******1234>
     *
     * @param num 手机号码字符串
     * @return 格式化后的手机号码字符串
     */
    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 address 地址字符串
     * @param sensitiveSize 敏感信息长度,即不显示的地址长度
     * @return 格式化后的地址字符串
     */
    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), "*");
    }

}

 编写测试实体类

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

package com.example.desensitization;


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;



    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public String getIdCard() {
        return idCard;
    }

    public void setIdCard(String idCard) {
        this.idCard = idCard;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

当我们想要应用的时候,只需要在想要脱敏的字段上加入注解即可!

测试程序如下:

package com.example.desensitization;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

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,结果如下:

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

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

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

念君思宁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值