SpringBoot实现返回值数据脱敏的步骤详解

 更新时间:2023年07月14日 10:57:07   作者:月色无痕  
这篇文章主要给大家介绍一下SpringBoot实现返回值数据脱敏的步骤,文章通过代码示例介绍的非常详细,具有一定的参考价值,需要的朋友可以参考下

介绍

  • SpringBoot实现返回数据脱敏

  • 有时,敏感数据返回时,需要进行隐藏处理,但是如果一个字段一个字段的进行硬编码处理的话,不仅增加了工作量,而且后期需求变动的时候,更加是地狱般的工作量变更。

  • 下面,通过身份证,姓名,密码,手机号等等示例去演示脱敏的流程,当然你也可以在此基础上添加自己的实现方式

原理

  • 项目使用的是SpringBoot,所以需要在序列化的时候,进行脱敏处理,springboot内置的序列化工具为jackson
  • 通过实现com.fasterxml.jackson.databind.JsonSerializer进行自定义序列化
  • 通过重写com.fasterxml.jackson.databind.ser.ContextualSerializer.createContextual获取自定义注解的信息

实现

自定义注解类

@Target(ElementType.FIELD) //作用于字段上
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside  // 表示自定义自己的注解Sensitive
@JsonSerialize(using = SensitiveInfoSerialize.class) // 该注解使用序列化的方式
public @interface Sensitive {
    SensitizedType value();
}

创建脱敏字段类型枚举

public enum SensitizedType {
    /**
     * 用户id
     */
    USER_ID,
    /**
     * 中文名
     */
    CHINESE_NAME,
    /**
     * 身份证号
     */
    ID_CARD,
    /**
     * 座机号
     */
    FIXED_PHONE,
    /**
     * 手机号
     */
    MOBILE_PHONE,
    /**
     * 地址
     */
    ADDRESS,
    /**
     * 电子邮件
     */
    EMAIL,
    /**
     * 密码
     */
    PASSWORD,
    /**
     * 中国大陆车牌,包含普通车辆、新能源车辆
     */
    CAR_LICENSE,
    /**
     * 银行卡
     */
    BANK_CARD,
    /**
     * IPv4地址
     */
    IPV4,
    /**
     * IPv6地址
     */
    IPV6,
    /**
     * 定义了一个first_mask的规则,只显示第一个字符。
     */
    FIRST_MASK
}

脱敏工具类

import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
/**
 * @Auther: wu
 * @Date: 2023/7/11
 * @Description: com.wu.demo.common.my_sensitive
 */
public class SensitizedUtil {
    public static String desensitized(CharSequence str, SensitizedType desensitizedType) {
        if (StrUtil.isBlank(str)) {
            return StrUtil.EMPTY;
        }
        String newStr = String.valueOf(str);
        switch (desensitizedType) {
            case USER_ID:
                newStr = String.valueOf(userId());
                break;
            case CHINESE_NAME:
                newStr = chineseName(String.valueOf(str));
                break;
            case ID_CARD:
                newStr = idCardNum(String.valueOf(str), 3, 4);
                break;
            case FIXED_PHONE:
                newStr = fixedPhone(String.valueOf(str));
                break;
            case MOBILE_PHONE:
                newStr = mobilePhone(String.valueOf(str));
                break;
            case ADDRESS:
                newStr = address(String.valueOf(str), 8);
                break;
            case EMAIL:
                newStr = email(String.valueOf(str));
                break;
            case PASSWORD:
                newStr = password(String.valueOf(str));
                break;
            case CAR_LICENSE:
                newStr = carLicense(String.valueOf(str));
                break;
            case BANK_CARD:
                newStr = bankCard(String.valueOf(str));
                break;
            case IPV4:
                newStr = ipv4(String.valueOf(str));
                break;
            case IPV6:
                newStr = ipv6(String.valueOf(str));
                break;
            case FIRST_MASK:
                newStr = firstMask(String.valueOf(str));
                break;
            default:
        }
        return newStr;
    }
    /**
     * 【用户id】不对外提供userId
     *
     * @return 脱敏后的主键
     */
    public static Long userId() {
        return 0L;
    }
    /**
     * 定义了一个first_mask的规则,只显示第一个字符。<br>
     * 脱敏前:123456789;脱敏后:1********。
     *
     * @param str 字符串
     * @return 脱敏后的字符串
     */
    public static String firstMask(String str) {
        if (StrUtil.isBlank(str)) {
            return StrUtil.EMPTY;
        }
        return StrUtil.hide(str, 1, str.length());
    }
    /**
     * 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**
     *
     * @param fullName 姓名
     * @return 脱敏后的姓名
     */
    public static String chineseName(String fullName) {
        return firstMask(fullName);
    }
    /**
     * 【身份证号】前1位 和后2位
     *
     * @param idCardNum 身份证
     * @param front     保留:前面的front位数;从1开始
     * @param end       保留:后面的end位数;从1开始
     * @return 脱敏后的身份证
     */
    public static String idCardNum(String idCardNum, int front, int end) {
        //身份证不能为空
        if (StrUtil.isBlank(idCardNum)) {
            return StrUtil.EMPTY;
        }
        //需要截取的长度不能大于身份证号长度
        if ((front + end) > idCardNum.length()) {
            return StrUtil.EMPTY;
        }
        //需要截取的不能小于0
        if (front < 0 || end < 0) {
            return StrUtil.EMPTY;
        }
        return StrUtil.hide(idCardNum, front, idCardNum.length() - end);
    }
    /**
     * 【固定电话 前四位,后两位
     *
     * @param num 固定电话
     * @return 脱敏后的固定电话;
     */
    public static String fixedPhone(String num) {
        if (StrUtil.isBlank(num)) {
            return StrUtil.EMPTY;
        }
        return StrUtil.hide(num, 4, num.length() - 2);
    }
    /**
     * 【手机号码】前三位,后4位,其他隐藏,比如135****2210
     *
     * @param num 移动电话;
     * @return 脱敏后的移动电话;
     */
    public static String mobilePhone(String num) {
        if (StrUtil.isBlank(num)) {
            return StrUtil.EMPTY;
        }
        return StrUtil.hide(num, 3, num.length() - 4);
    }
    /**
     * 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****
     *
     * @param address       家庭住址
     * @param sensitiveSize 敏感信息长度
     * @return 脱敏后的家庭地址
     */
    public static String address(String address, int sensitiveSize) {
        if (StrUtil.isBlank(address)) {
            return StrUtil.EMPTY;
        }
        int length = address.length();
        return StrUtil.hide(address, length - sensitiveSize, length);
    }
    /**
     * 【电子邮箱】邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com
     *
     * @param email 邮箱
     * @return 脱敏后的邮箱
     */
    public static String email(String email) {
        if (StrUtil.isBlank(email)) {
            return StrUtil.EMPTY;
        }
        int index = StrUtil.indexOf(email, '@');
        if (index <= 1) {
            return email;
        }
        return StrUtil.hide(email, 1, index);
    }
    /**
     * 【密码】密码的全部字符都用*代替,比如:******
     *
     * @param password 密码
     * @return 脱敏后的密码
     */
    public static String password(String password) {
        if (StrUtil.isBlank(password)) {
            return StrUtil.EMPTY;
        }
        return StrUtil.repeat('*', password.length());
    }
    /**
     * 【中国车牌】车牌中间用*代替
     * eg1:null       -》 ""
     * eg1:""         -》 ""
     * eg3:苏D40000   -》 苏D4***0
     * eg4:陕A12345D  -》 陕A1****D
     * eg5:京A123     -》 京A123     如果是错误的车牌,不处理
     *
     * @param carLicense 完整的车牌号
     * @return 脱敏后的车牌
     */
    public static String carLicense(String carLicense) {
        if (StrUtil.isBlank(carLicense)) {
            return StrUtil.EMPTY;
        }
        // 普通车牌
        if (carLicense.length() == 7) {
            carLicense = StrUtil.hide(carLicense, 3, 6);
        } else if (carLicense.length() == 8) {
            // 新能源车牌
            carLicense = StrUtil.hide(carLicense, 3, 7);
        }
        return carLicense;
    }
    /**
     * 银行卡号脱敏
     * eg: 1101 **** **** **** 3256
     *
     * @param bankCardNo 银行卡号
     * @return 脱敏之后的银行卡号
     * @since 5.6.3
     */
    public static String bankCard(String bankCardNo) {
        if (StrUtil.isBlank(bankCardNo)) {
            return bankCardNo;
        }
        bankCardNo = StrUtil.trim(bankCardNo);
        if (bankCardNo.length() < 9) {
            return bankCardNo;
        }
        final int length = bankCardNo.length();
        final int midLength = length - 8;
        final StringBuilder buf = new StringBuilder();
        buf.append(bankCardNo, 0, 4);
        for (int i = 0; i < midLength; ++i) {
            if (i % 4 == 0) {
                buf.append(CharUtil.SPACE);
            }
            buf.append('*');
        }
        buf.append(CharUtil.SPACE).append(bankCardNo, length - 4, length);
        return buf.toString();
    }
    /**
     * IPv4脱敏,如:脱敏前:192.0.2.1;脱敏后:192.*.*.*。
     *
     * @param ipv4 IPv4地址
     * @return 脱敏后的地址
     */
    public static String ipv4(String ipv4) {
        return StrUtil.subBefore(ipv4, '.', false) + ".*.*.*";
    }
    /**
     * IPv4脱敏,如:脱敏前:2001:0db8:86a3:08d3:1319:8a2e:0370:7344;脱敏后:2001:*:*:*:*:*:*:*
     *
     * @param ipv6 IPv4地址
     * @return 脱敏后的地址
     */
    public static String ipv6(String ipv6) {
        return StrUtil.subBefore(ipv6, ':', false) + ":*:*:*:*:*:*:*";
    }
}

上述枚举类和脱敏工具类,我使用了hutool中的代码,如果hutool满足你的需求,可以直接把上述自定义注解类和自定义序列化类使用到的SensitizedType类直接替换为hutool中的cn.hutool.core.util.DesensitizedUtil.DesensitizedType的枚举类,

添加自定义序列化实现类

public class SensitiveInfoSerialize extends JsonSerializer<String> implements ContextualSerializer {
    private SensitizedType sensitizedType;
    /**
     * 步骤一
     * 方法来源于ContextualSerializer,获取属性上的注解属性,同时返回一个合适的序列化器
     */
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        // 获取自定义注解
        Sensitive annotation = beanProperty.getAnnotation(Sensitive.class);
        // 注解不为空,且标注的字段为String
        if(Objects.nonNull(annotation) && Objects.equals(String.class, beanProperty.getType().getRawClass())){
            this.sensitizedType = annotation.value();
            //自定义情况,返回本序列化器,将顺利进入到该类中的serialize方法中
            return this;
        }
        // 注解为空,字段不为String,寻找合适的序列化器进行处理
        return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
    }
    /**
     * 步骤二
     * 方法来源于JsonSerializer<String>:指定返回类型为String类型,serialize()将修改后的数据返回
     */
    @Override
    public void serialize(String str, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        if(Objects.isNull(sensitizedType)){
            // 定义策略为空,返回原字符串
            jsonGenerator.writeString(str);
        }else {
            // 定义策略不为空,返回策略处理过的字符串
            jsonGenerator.writeString(SensitizedUtil.desensitized(str,sensitizedType));
        }
    }
}

测试验证

在需要的脱敏的实体类字段上加上相应的注解

@Data
public class SensitiveBody {
    private String name;
    @Sensitive(SensitizedType.MOBILE_PHONE)
    private String mobile;
    @Sensitive(SensitizedType.ID_CARD)
    private String idCard;
}
    @ApiOperation(value = "脱敏测试处理")
    @GetMapping("sensitiveTest")
    public AjaxResult sensitiveTest(){
        SensitiveBody body = new SensitiveBody();
        body.setMobile("13041064026");
        body.setIdCard("411126189912355689");
        body.setName("Tom");
        return AjaxResult.success(body);
    }

到此这篇关于SpringBoot实现返回值数据脱敏的步骤详解的文章就介绍到这了,更多相关SpringBoot返回值数据脱敏内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot+log4j2.xml使用application.yml属性值问题

    SpringBoot+log4j2.xml使用application.yml属性值问题

    这篇文章主要介绍了SpringBoot+log4j2.xml使用application.yml属性值问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • Java数据库连接池之proxool_动力节点Java学院整理

    Java数据库连接池之proxool_动力节点Java学院整理

    Proxool是一种Java数据库连接池技术。方便易用,便于发现连接泄漏的情况
    2017-08-08
  • Java网络编程基础教程之Socket入门实例

    Java网络编程基础教程之Socket入门实例

    这篇文章主要介绍了Java网络编程基础教程之Socket入门实例,本文讲解了创建Socket、Socket发送数据、Socket读取数据、关闭Socket等内容,都是最基础的知识点,需要的朋友可以参考下
    2014-09-09
  • Java执行Linux命令简单代码举例

    Java执行Linux命令简单代码举例

    这篇文章主要给大家介绍了关于Java执行Linux命令的相关资料,在开发的过程中要善于利用JAVA面向对象编程的优势,与Linux/Unix命令或Shell脚本的优势,并将二者相结合,需要的朋友可以参考下
    2023-12-12
  • 关于Spring MVC同名参数绑定问题的解决方法

    关于Spring MVC同名参数绑定问题的解决方法

    Spring MVC中的参数绑定还是蛮重要的,最近在使用中遇到了同名参数绑定的问题,想着总结分享出来,下面这篇文章主要给大家介绍了关于Spring MVC同名参数绑定问题的解决方法,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-08-08
  • Java POI实现将导入Excel文件的示例代码

    Java POI实现将导入Excel文件的示例代码

    这篇文章主要介绍了Java POI实现将导入Excel文件的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-02-02
  • springboot-dubbo cannot be cast to问题及解决

    springboot-dubbo cannot be cast to问题及解决

    这篇文章主要介绍了springboot-dubbo cannot be cast to问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • 使用Java把文本内容转换成网页的实现方法分享

    使用Java把文本内容转换成网页的实现方法分享

    这篇文章主要介绍了使用Java把文本内容转换成网页的实现方法分享,利用到了Java中的文件io包,需要的朋友可以参考下
    2015-11-11
  • Java使用ArrayList实现扑克牌的示例代码

    Java使用ArrayList实现扑克牌的示例代码

    学习了关于集合类的知识,我们可以做一个小项目来加深对集合类知识的学习!本文就来利用ArrayList实现扑克牌发牌洗牌效果,需要的可以参考一下
    2022-10-10
  • 详解Java中的Lambda表达式

    详解Java中的Lambda表达式

    这篇文章主要介绍了Java中的Lambda表达式的的相关资料,文中讲解非常详细,示例代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-06-06

最新评论