springboot实现数据脱敏响应请求

背景:

由于业务需要,敏感数据外流时,需要进行隐藏处理,避免敏感信息隐私数 据泄露导致用户信息安全收到影响。

类型:

  1. 数据脱敏分为静态数据脱敏(SDM)和动态数据脱敏(DDM)
  2. 根据应用场景划分,数据脱敏能够应用在后端->浏览器和数据库两种场 景。

介绍:

  1. 静态数据脱敏

将数据从生产环境中抽取进行脱敏后分发给测试、开发、培训、数据分 析等场景。

即从生产环境中将数据copy下来,再通过脱敏处理,在隐藏敏感信息的 同时又保留的业务逻辑之间的关联,将脱敏后的数据用来测试开发分析。

作用场景:数据库

  1. 动态数据脱敏

一般用在生产环境、访问敏感信息数据时实时进行脱敏,在不同的情况 下对于同一敏感数据的读取,需要做不同级别的脱敏处理

  1. 不同角色、不同权限所执行的脱敏方案会不同。

作用场景:后端->浏览器、数据库

使用逻辑:

  1. 后端------>浏览器数据从后端传输到浏览器,使用脱敏方式隐藏字段 关键信息传输。
  2. 数据库:对于外流数据,使用6种方式对其进行处理

方案:      

  1. 后端:Jackson技术实现数据脱敏,本质上是使用无效化中的字符隐藏
  2. 数据库:
  1. 无效化:对字段数据值进行 截断、加密、隐藏 等方式让敏感数据脱敏,使其不再具有利用价值。一般使用“*”隐藏。
  2. 随机值:随机值替换,字母变为随机字母,数字变为随机数字,文字随机替换文字的方式来改变敏感数据
  3. 数据替换:统一设置某一字段数据,比如将手机号全部设置为“12345678912”
  4. 对称加密:利用加密密钥和算法对敏感数据进行加密,是一种可逆的加密脱敏方式。需要注意密钥的安全性。
  5. 平均值:平均值方案经常用在统计场景,针对数值型数据,我们先计算它们的均值,然后使脱敏后的值在均值附近随机分布,从而保持数据的总和不变。
  6. 偏移和取整:这种方式通过随机移位改变数字数据,偏移取整在保持了数据的安全性的同时保证了范围的大致真实性,比之前几种方案更接近真实数据,在大数据分析场景中意义比较大。

代码实现:

自定义脱敏配置类(配置数据类型、前置不需要打码的长度、后置不需要打码的长度、用什么字符进行打码)

package com.example.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 "*";
}

脱敏配置枚举类(定义需要进行脱敏的字段,以及自定义字段Customer-能够直接通过注解配置设置脱敏)

package com.example.PrivacyEncrypt;



import lombok.Getter;



//定义需要脱敏的属性枚举

@Getter

public enum PrivacyTypeEnum {

    /** 自定义(此项需设置脱敏的范围),也可以设置脱敏使用的字符*/

    CUSTOMER,



    /** 姓名 */

    username,



    /** 身份证号 */

    card,



    /** 手机号 */

    phone,



    /** 邮箱 */

    email,



/*    密码,快捷键Ctrl+Shift+/*/

    password,



}

序列化配置类(配置枚举中的属性使用什么方法进行脱敏)

package com.example.PrivacyEncrypt;

import com.example.utils.PrivacyUtil;

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 username:

                    jsonGenerator.writeString(PrivacyUtil.hideChineseName(origin));

                    break;

                case 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);

    }

}

脱敏方法工具类(设置脱敏方法供属性使用,以及设置字符串方法)

package com.example.utils;



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

    }





}

实体类(使用脱敏注解的数据类,通过注解绑定枚举,通过枚举绑定脱敏类型加工数据、还可以通过注解直接设置自定义脱敏规则脱敏字段)

package com.example.entity;

import com.baomidou.mybatisplus.annotation.IdType;

import com.baomidou.mybatisplus.annotation.TableField;

import com.baomidou.mybatisplus.annotation.TableId;

import com.baomidou.mybatisplus.annotation.TableName;

import com.example.PrivacyEncrypt.PrivacyEncrypt;

import com.example.PrivacyEncrypt.PrivacyTypeEnum;

import io.swagger.annotations.ApiModelProperty;

import lombok.AllArgsConstructor;

import lombok.Data;

import lombok.NoArgsConstructor;



@Data

@NoArgsConstructor

@AllArgsConstructor

@TableName(value = "usertest")

public class User {

    @TableId(value = "id",type = IdType.AUTO)

    @ApiModelProperty(value = "主键")

    private int id;



    @TableField(value = "username")

    @ApiModelProperty(value = "姓名")

    @PrivacyEncrypt(type = PrivacyTypeEnum.username)

    private String username;



    @TableField(value = "password")

    @ApiModelProperty(value = "密码")

    @PrivacyEncrypt(type = PrivacyTypeEnum.CUSTOMER,prefixNoMaskLen = 0,suffixNoMaskLen = 0,symbol = "*")

    private String password;

//自定义脱敏方式,prefixNoMaskLen前面字符显示为0,suffixNoMaskLen后面字符显示为0,用"*"覆盖



    @TableField(value = "phone")

    @ApiModelProperty(value = "电话")

    @PrivacyEncrypt(type = PrivacyTypeEnum.phone)

    private String phone;



    @TableField(value = "email")

    @ApiModelProperty(value = "邮箱")

    @PrivacyEncrypt(type = PrivacyTypeEnum.email)

    private String email;



    @TableField(value = "card")

    @ApiModelProperty(value = "身份证号码")

    @PrivacyEncrypt(type = PrivacyTypeEnum.card)

    private String card;

}

开发以及测试工具:

idea2018,postman

技术架构:

SpringBoot、mybatis-plus

参考资料:

https://blog.csdn.net/weixin_44792849/article/details/128566090

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值