IT俱乐部 Java SpringBoot实现返回值数据脱敏的步骤详解

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

介绍

  • SpringBoot实现返回数据脱敏

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

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

原理

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

实现

自定义注解类

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

创建脱敏字段类型枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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
}

脱敏工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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

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

添加自定义序列化实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class SensitiveInfoSerialize extends JsonSerializer 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类型,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));
        }
    }
}

测试验证

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

1
2
3
4
5
6
7
8
@Data
public class SensitiveBody {
    private String name;
    @Sensitive(SensitizedType.MOBILE_PHONE)
    private String mobile;
    @Sensitive(SensitizedType.ID_CARD)
    private String idCard;
}
1
2
3
4
5
6
7
8
9
@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返回值数据脱敏内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!

本文收集自网络,不代表IT俱乐部立场,转载请注明出处。https://www.2it.club/code/java/8759.html
上一篇
下一篇
联系我们

联系我们

在线咨询: QQ交谈

邮箱: 1120393934@qq.com

工作时间:周一至周五,9:00-17:30,节假日休息

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部