SpringBoot 实现接口数据脱敏

数据脱敏系列

数据脱敏(Data Masking)是一种通过特定规则对敏感信息进行变形处理的技术,旨在保护隐私数据的安全。


接口数据脱敏是在前端显示敏感数据时,比如手机号、身份证号、邮箱等,将其部分字符替换为星号或其他符号,以保护用户隐私。

其中还可以穿插权限控制,有权限的用户可以看到明文数据,而没有权限的用户只能看到脱敏后的字符串。


实现

创建自定义注解

其中 @JsonSerialize 中配置自定义了序列化器

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerializer.class)
public @interface Sensitive {
    // 脱敏类型
    SensitiveType type() default SensitiveType.DEFAULT;
    // 前置保留位数(如手机号前3位)
    int prefix() default -1;
    // 后置保留位数(如身份证后4位)
    int suffix() default -1;
    // 替换字符
    String mask() default "*";
}

定义脱敏类型

设置默认的脱敏规则

@RequiredArgsConstructor
@Getter
public enum SensitiveType {

    // 默认,全部隐藏,显示****
    DEFAULT(0, 0, "****"),
    // 自定义,需要手动设置前置后置长度
    CUSTOMER(-1, -1, "*"),
    // 用户名, 只显示第一位,其它隐藏,比如:徐*
    CHINESE_NAME(1, 0, "*"),
    // 身份证号, 显示前六后四,其它隐藏,比如:110110********1234
    ID_CARD(6, 4, "*"),
    // 座机号,显示后四位,其它隐藏,比如:****1234
    PHONE(0, 4, "*"),
    // 手机号, 显示前三后四,其它隐藏,比如:176****1234
    MOBILE(3, 4, "*"),
    // 地址, 显示前六,其它隐藏,比如:北京市海淀区********
    ADDRESS(6, 0, "******"),
    // 电子邮件, @ 前显示第一位,其它隐藏,@ 后全显示,比如:s*****o@126.com
    EMAIL(1, 0, "****"),
    // 银行卡, 显示前六后四,其它隐藏,比如:622202************1234
    BANK_CARD(6, 4, "*"),
    // 密码, 永远是 ******, 与长度无关
    PASSWORD(0, 0, "******"),
    ;

    private final Integer prefix;
    private final Integer suffix;
    private final String mask;
}

编写脱敏方法

public class SensitiveUtils {

    /**
     * 邮箱
     * 显示规则:{@code @} 前只显示一位,其它隐藏,{@code @} 后全部显示,示例:{@code d**@126.com}
     *
     * @param email 邮箱
     * @return 脱敏字符串
     */
    public static String email(String email, SensitiveType type) {
        if (email == null) {
            return null;
        }
        int index = StringUtils.indexOf(email, StringPool.AT);
        if (index <= 1) {
            return email;
        }
        String prefix = mask(StringUtils.substring(email, 0, index), type);
        return prefix + email.substring(index);
    }

    public static String mask(String input, SensitiveType type) {
        return mask(input, type.getPrefix(), type.getSuffix(), type.getMask());
    }

    public static String mask(String input, int prefix, int suffix, String mask) {
        if (StringUtils.isEmpty(input)) {
            return input;
        }
        boolean customMask = mask.length() > 1;
        StringBuilder sb = new StringBuilder();
        for (int i = 0, n = input.length(); i < n; i++) {
            if (i < prefix) {
                sb.append(input.charAt(i));
                continue;
            }
            if (i > (n - suffix - 1)) {
                sb.append(input.charAt(i));
                continue;
            }
            if (customMask) {
                if (i == prefix) {
                    sb.append(mask);
                }
            } else {
                sb.append(mask);
            }
        }
        return sb.toString();
    }
}

创建自定义序列化类

@NoArgsConstructor
@AllArgsConstructor
public class SensitiveSerializer extends JsonSerializer<String> implements ContextualSerializer {

    private SensitiveType type;
    private int prefix;
    private int suffix;
    private String mask;

    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeString(sensitive((String) value));
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        if (beanProperty != null) {
            Sensitive sensitive = beanProperty.getAnnotation(Sensitive.class);
            if (sensitive == null) {
                sensitive = beanProperty.getContextAnnotation(Sensitive.class);
            }
            if (sensitive != null) {
                return new SensitiveSerializer(sensitive.type(), sensitive.prefix(), sensitive.suffix(), sensitive.mask());
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(null);
    }

    private String sensitive(String value) {
        switch (type) {
            case EMAIL: return SensitiveUtils.email(value, type);
            case CUSTOMER: return SensitiveUtils.mask(value, prefix, suffix, mask);
            default : return SensitiveUtils.mask(value, type);
        }
    }
}

使用

public class User {

    private String name;
    @Sensitive(type = SensitiveType.MOBILE)
    private String mobile;
    @Sensitive(type = SensitiveType.ID_CARD)
    private String idCard;
    @Sensitive(type = SensitiveType.EMAIL)
    private String email;
}

测试

@RestController
public class UserController {

    @GetMapping("/user")
    public User getUser() {
        User user = new User();
        user.setName("张三");
        user.setMobile("13812345678");
        user.setIdCard("123456789012345678");
        user.setEmail("example@126.com");
        return user;
    }
}

访问输出如下

{
    "name": "张三",
    "mobile": "138****5678",
    "idCard": "123456******5678",
    "email": "e****@example.com"
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值