如何实现数据脱敏?

一、前言

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

  1. 手机号中间四位用*显示,比如:17801234118 → 178****0118
  2. 身份证中间八位用*显示,比如:450722199901012069 → 450722********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注解即可。

相关推荐
蛋蛋的就会顺顺的2 小时前
hot100 p0——栈,贪心算法,动态规划
java·前端·javascript·算法·leetcode·力扣
余槐i2 小时前
PostgreSQL 函数索引报错 must be immutable:3种绕法与代价
数据库·后端·sql·postgresql·性能优化
需要8263 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
小 黄 鸡3 小时前
HashMap 面试必会知识点详解(JDK 8)
java·面试·职场和发展
凤山老林4 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
伯恩bourne5 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
张彦峰ZYF5 小时前
停顿短不等于延迟低:G1、ZGC 与低延迟服务的目标分野、代价账本与选型标定
后端·回收暂停、应用停止总时长·到达安全点、分配停滞·屏障吃吞吐、并发线程吃核心·并发窗口吃堆余量·分区式靠挑回收集兑现目标·染色指针式不支持压缩引用
silvamux_5 小时前
curl 测试 OpenAI 兼容 API 的完整流程
java·人工智能·缓存·ai·oneapi
pride.li5 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
省钱兄--zs5 小时前
深圳24小时自助健身房软硬件解决方案实战指南与开发流程详解
java·数据仓库·spring boot·小程序·架构