如何实现数据脱敏?

一、前言

什么是数据脱敏呢?我相信小伙伴们多多少少都遇到过或者听到过以下这些需求:

  1. 手机号中间四位用*显示,比如:17801234118178****0118
  2. 身份证中间八位用*显示,比如:450722199901012069450722********2069

这里只列举了两个例子,还有很多场景的,比如银行卡、姓名这些,需求要求只展示部分数据,但是不能全部都展示出来,防止敏感信息泄露,这就是数据脱敏

二、实现数据脱敏的方案

方案一:使用Hutool提供的工具类

这个方式应该是最简便的了,拿来即用。

DesensitizedUtil工具类中还提供有很多场景下的数据脱敏方法,大家可以自行测试。

方案二:利用正则表达式处理

java 复制代码
public class SensitiveUtil {

    public static String maskPhone(String phone) {
        if (phone.length() != 11) {
            return phone;
        }
        return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    }

    public static String maskIdCard(String idCard) {
        if (idCard.length() < 15) {
            return idCard;
        }
        return idCard.replaceAll("(\\d{6})\\d{8}(\\w{4})", "$1********$2");
    }

    public static String maskEmail(String email) {
        if (!email.contains("@")) {
            return email;
        }
        String[] parts = email.split("@");
        if (parts[0].length() <= 4) {
            return parts[0].substring(0, 1) + "****@" + parts[1];
        }
        return parts[0].substring(0, 4) + "****@" + parts[1];
    }

    public static String maskBankCard(String bankCard) {
        if (bankCard.length() < 8) {
            return bankCard;
        }
        return bankCard.replaceAll("(\\d{4})\\d{8}(\\d{4})", "$1********$2");
    }
}

方案三:自定义注解+AOP实现数据脱敏

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Desensitize {
    // 定义脱敏类型,比如手机号、银行卡号、身份证号等
    DesensitizeType type();
}
java 复制代码
public enum DesensitizeType {
    PHONE,       // 手机号
    ID_CARD,     // 身份证号
    BANK_CARD    // 银行卡号
}
java 复制代码
@Component
public class DesensitizeProcessor {
    // 根据传入的值和脱敏类型,进行数据脱敏
    public String desensitize(String value, DesensitizeType type) {
        // 根据不同的脱敏类型,执行不同的脱敏逻辑
        switch (type) {
            case PHONE:
                // 把手机号中间四位替换成****
                return value.replaceAll("(\d{3})\d{4}(\d{4})", "$1****$2");
            case ID_CARD:
                // 把身份证号中间八位替换成********
                return value.replaceAll("(\d{6})\d{8}(\d{4})", "$1********$2");
            case BANK_CARD:
                // 把银行卡号中间十二位替换成************
                return value.replaceAll("(\d{4})\d{12}(\d{4})", "$1************$2");
            default:
                return value;
        }
    }
}

大家可以看到,实际进行数据脱敏处理的逻辑,跟第二个方案中的是一样的,都是运用了正则表达式。

java 复制代码
@Aspect
@Component
public class DesensitizeAspect {
    @Autowired
    private DesensitizeProcessor desensitizeProcessor;

    /**
     * 定义切面的切点,当方法上有@ResponseBody时触发
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("@annotation(org.springframework.web.bind.annotation.ResponseBody)")
    public Object desensitize(ProceedingJoinPoint joinPoint) throws Throwable {
        // 先执行目标方法,获取返回结果
        Object result = joinPoint.proceed();
        if (result != null) {
            // 对返回结果进行脱敏处理
            handleObject(result);
        }
        return result;
    }
    private void handleObject(Object obj) {
        Class<?> clazz = obj.getClass();
        // 获取类的所有字段
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 获取字段上的Desensitize注解
            Desensitize desensitize = field.getAnnotation(Desensitize.class);
            if (desensitize != null) {
                // 设置字段可访问
                field.setAccessible(true);
                try {
                    // 获取字段值
                    Object value = field.get(obj);
                    if (value instanceof String) {
                        // 如果值是字符串类型,进行脱敏处理
                        String res = desensitizeProcessor.desensitize((String) value, desensitize.type());
                        // 把脱敏后的值重新设置回字段
                        field.set(obj, res);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            // 如果字段类型是复杂对象,则进行递归处理
            if (isComplexObject(field.getType())) {
                handleObject(ReflectionUtils.getField(field, obj));
            }
        }
    }

    /**
     * 用于判断一个类是否为复杂对象。它通过以下条件进行判断:
     * 不是基本数据类型(如int、boolean等)
     * 不是String类
     * 不是枚举类型
     * 是一个有效的类(非接口、注解等)
     * 满足以上所有条件时,该方法返回true,表示这是一个复杂对象。
     * @param clazz
     * @return
     */
    private boolean isComplexObject(Class<?> clazz) {
        return !clazz.isPrimitive() && !clazz.equals(String.class) && !clazz.isEnum() && clazz.isClass();
    }
}

在接口返回数据给前端时,该切面类会检查返回对象中是否有带有@Desensitize注解的字段,有的话则用DesensitizeProcessor进行脱敏处理。

我们只需要在需要脱敏的字段加上@Desensitize注解即可。

相关推荐
葫芦和十三15 分钟前
图解 MongoDB 21|选举与 failover:Primary 是怎么选出来的
后端·mongodb·agent
GetcharZp42 分钟前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑1 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯2 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan4 小时前
多Agent之间的区别
后端
青石路6 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充6 小时前
1.面向对象设计思想
后端
IT_陈寒7 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro7 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
要阿尔卑斯吗7 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端