• 数据脱敏是怎么做的


    数据脱敏是怎么做的

    什么是数据脱敏?

    数据脱敏就是对敏感数据(手机号、身份证、银行卡、地址等)进行不可逆或可逆的变形处理,在不影响业务正常使用的前提下,防止数据泄露。

    核心三原则:

    • 不可逆性:脱敏后的数据无法还原为原始数据(静态脱敏)
    • 一致性:相同原始数据脱敏后结果一致(保证统计分析可用)
    • 可用性:脱敏后的数据保留原有格式和业务特征(如手机号保留前 3 后 4)

    常用脱敏算法对比📊

    算法类型 实现原理 适用场景 优点 缺点
    掩码脱敏 保留部分字符,其余用 * 代替 前端展示、日志打印 最简单、性能最好 安全性最低,易被暴力破解
    替换脱敏 用随机字符 / 字典替换原始数据 测试环境、数据分析 保留数据格式和业务逻辑 字典维护成本高
    哈希脱敏 MD5/SHA256 + 盐值计算 用户密码、唯一标识 不可逆,安全性高 相同输入输出相同,易被彩虹表攻击
    加密脱敏 AES/RSA 对称 / 非对称加密 传输过程、需要还原的场景 可还原,安全性最高 性能最差,密钥管理复杂
    截断脱敏 只保留数据的前 N 位或后 N 位 非核心字段展示 简单高效 丢失数据完整性
    洗牌脱敏 打乱数据内部字符顺序 地址、姓名 保留数据长度和字符集 可能保留部分语义信息

    关于这个问题的底层原理和更多实战细节,我整理了一份《大厂面试手册》,包含大厂高频面试题、源码解析和性能调优案例。
    关注公众号【Rain的Java大神之路】,回复“Java”即可免费领取,持续更新中。


    企业级分层脱敏架构🏗️

    我们团队采用的是分层脱敏方案,在数据流转的各个环节都做防护,避免单点失效。

    image

    各层具体实现:

    1. 前端展示层:前端组件统一处理,只展示脱敏后的数据,原始数据不传到前端
    2. 接口层:使用 Spring AOP 拦截所有接口响应,对标注了@Sensitive注解的字段自动脱敏
    3. 日志层:重写 logback 的MessageConverter,自动过滤日志中的敏感数据
    4. 数据访问层:基于 MyBatis 的Interceptor实现查询结果的自动脱敏和插入前的加密
    5. 数据库层:生产库定期导出到测试库时,使用脚本批量静态脱敏

    关键技术难点与解决方案⚠️

    1.动态脱敏问题

    • 问题:不同角色看到的脱敏程度不同(管理员看完整数据,普通员工看脱敏数据)
    • 解决方案:基于 Spring Security 的权限体系,在 AOP 中根据当前用户角色动态选择脱敏策略

    2.脱敏与业务解耦

    • 问题:如果在业务代码中硬编码脱敏逻辑,会导致代码混乱且难以维护
    • 解决方案:自定义@Sensitive注解,配合 AOP 实现声明式脱敏,业务代码无感知
    // 自定义脱敏注解
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Sensitive {
        SensitiveType value(); // 脱敏类型:PHONE, ID_CARD, BANK_CARD等
    }
    
    // 使用示例
    public class User {
        private Long id;
        
        @Sensitive(SensitiveType.PHONE)
        private String phone;
        
        @Sensitive(SensitiveType.ID_CARD)
        private String idCard;
    }
    

    3.大数据场景下的脱敏

    • 问题:Hive、Spark 等大数据平台处理海量数据时,传统脱敏方式性能不足
    • 解决方案:使用 UDF 函数实现分布式脱敏,在数据写入时就完成脱敏处理

    总结✅

    数据脱敏不是单一技术,而是一个体系化的工程。最好的实践是:

    • 静态脱敏用于测试环境和数据分析
    • 动态脱敏用于生产环境的实时访问
    • 结合权限控制,实现细粒度的数据保护
    • 定期审计脱敏效果,及时发现漏洞

    这样既保证了数据安全,又不会对业务性能造成太大影响。

    核心代码实现(带技术亮点✨)

    我们采用声明式 + 策略模式的架构,实现了业务代码零侵入的脱敏方案,支持动态扩展脱敏算法。

    1. 基础定义层(技术亮点:开闭原则设计)

    /**
     * 脱敏类型枚举
     * 新增脱敏类型只需在这里添加,无需修改原有业务代码
     */
    public enum SensitiveType {
        PHONE,       // 手机号
        ID_CARD,     // 身份证
        BANK_CARD,   // 银行卡
        EMAIL,       // 邮箱
        NAME,        // 姓名
        ADDRESS,     // 地址
        CUSTOM       // 自定义策略
    }
    
    /**
     * 脱敏策略接口(策略模式核心)
     * 所有脱敏算法都实现此接口,支持Spring自动注入
     */
    public interface SensitiveStrategy {
        String desensitize(String original);
    }
    

    2. 默认脱敏策略实现

    /**
     * 手机号脱敏策略
     * 保留前3后4位:138****1234
     */
    @Component
    public class PhoneSensitiveStrategy implements SensitiveStrategy {
        @Override
        public String desensitize(String original) {
            if (original == null || original.length() != 11) {
                return original;
            }
            return original.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
        }
    }
    
    /**
     * 身份证脱敏策略
     * 保留前6后4位:110101********1234
     */
    @Component
    public class IdCardSensitiveStrategy implements SensitiveStrategy {
        @Override
        public String desensitize(String original) {
            if (original == null || original.length() != 18) {
                return original;
            }
            return original.replaceAll("(\\d{6})\\d{8}(\\d{4})", "$1********$2");
        }
    }
    

    3. 自定义脱敏注解

    /**
     * 敏感字段注解
     * 标注在实体类字段上,自动触发脱敏
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Sensitive {
        SensitiveType value() default SensitiveType.CUSTOM;
        
        // 支持自定义脱敏策略类(扩展点)
        Classextends SensitiveStrategy> strategy() default SensitiveStrategy.class;
    }
    

    4. Spring AOP 动态脱敏切面(核心技术亮点🔥)

    /**
     * 全局响应脱敏切面
     * 技术亮点:
     * 1. 结合Spring Security实现基于角色的动态脱敏
     * 2. 递归处理嵌套对象和集合,支持复杂数据结构
     * 3. 缓存脱敏策略实例,避免重复反射创建
     */
    @Aspect
    @Component
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public class SensitiveAspect {
        @Autowired
        private ApplicationContext applicationContext;
        
        // 缓存脱敏策略实例
        private final Mapextends SensitiveStrategy>, SensitiveStrategy> strategyCache = new ConcurrentHashMap<>();
        
        @Around("@annotation(org.springframework.web.bind.annotation.ResponseBody)")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            Object result = joinPoint.proceed();
            if (result == null) {
                return null;
            }
            
            // 获取当前用户角色
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            boolean isAdmin = auth != null && auth.getAuthorities().stream()
                    .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
            
            // 管理员不脱敏
            if (isAdmin) {
                return result;
            }
            
            // 递归脱敏处理
            desensitizeObject(result);
            return result;
        }
        
        private void desensitizeObject(Object obj) throws IllegalAccessException {
            if (obj == null || obj.getClass().isPrimitive() || obj instanceof String) {
                return;
            }
            
            // 处理集合
            if (obj instanceof Collection) {
                for (Object item : (Collection) obj) {
                    desensitizeObject(item);
                }
                return;
            }
            
            // 处理实体类字段
            for (Field field : obj.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                Sensitive sensitive = field.getAnnotation(Sensitive.class);
                
                if (sensitive != null && field.getType() == String.class) {
                    String original = (String) field.get(obj);
                    String desensitized = getStrategy(sensitive).desensitize(original);
                    field.set(obj, desensitized);
                } else {
                    // 递归处理嵌套对象
                    desensitizeObject(field.get(obj));
                }
            }
        }
        
        // 从缓存获取策略实例
        private SensitiveStrategy getStrategy(Sensitive sensitive) {
            Classextends SensitiveStrategy> strategyClass = sensitive.strategy();
            if (strategyClass == SensitiveStrategy.class) {
                // 根据类型获取默认策略
                String beanName = sensitive.value().name().toLowerCase() + "SensitiveStrategy";
                return applicationContext.getBean(beanName, SensitiveStrategy.class);
            }
            return strategyCache.computeIfAbsent(strategyClass, applicationContext::getBean);
        }
    }
    

    5. Logback 全局日志脱敏(容易忽略的安全点⚠️)

    /**
     * 日志脱敏转换器
     * 技术亮点:全局统一处理,避免业务代码中手动过滤敏感数据
     */
    public class SensitiveLogConverter extends ClassicConverter {
        // 匹配手机号、身份证、银行卡的正则表达式
        private static final Pattern[] PATTERNS = {
            Pattern.compile("1[3-9]\\d{9}"),
            Pattern.compile("\\d{17}[\\dXx]"),
            Pattern.compile("\\d{16,19}")
        };
        
        @Override
        public String convert(ILoggingEvent event) {
            String message = event.getFormattedMessage();
            for (Pattern pattern : PATTERNS) {
                Matcher matcher = pattern.matcher(message);
                StringBuffer sb = new StringBuffer();
                while (matcher.find()) {
                    // 统一替换为****
                    matcher.appendReplacement(sb, "****");
                }
                matcher.appendTail(sb);
                message = sb.toString();
            }
            return message;
        }
    }
    

    技术难点与解决方案汇总📋

    技术难点 问题影响 解决方案 核心技术亮点
    动态权限脱敏 不同角色看到不同脱敏程度的数据,硬编码会导致代码混乱 基于 Spring Security + AOP,在切面中判断用户角色动态选择脱敏策略 角色与脱敏逻辑解耦,支持细粒度权限控制
    脱敏后数据关联问题 哈希脱敏后,相同原始数据需要保持相同结果才能进行关联查询 使用固定盐值 + SHA256哈希,保证一致性;需要还原的场景使用 AES 可逆加密 兼顾安全性和业务可用性
    批量数据脱敏性能 千万级数据批量脱敏时,单线程处理耗时过长 使用 MyBatis 批处理 + 多线程池,大数据场景使用 Spark UDF 分布式脱敏 性能提升 10 倍以上,支持 TB 级数据处理
    密钥管理问题 加密脱敏的密钥硬编码在代码中,存在泄露风险 使用阿里云 KMS/HashiCorp Vault 管理密钥,实现密钥自动轮换 符合等保三级要求,密钥生命周期管理
    第三方接口数据脱敏 调用第三方接口时,需要传递部分敏感数据 建立统一的 API 网关,在网关层对出入参进行自动脱敏 / 加密 业务代码无感知,统一管控所有外部调用
    数据导出脱敏 Excel/PDF 导出时容易泄露完整敏感数据 重写 POI 导出工具类,自动识别 @Sensitive 注解并脱敏 与现有导出逻辑无缝集成
    脱敏规则变更 业务变更需要修改脱敏规则时,需要重新发布代码 将脱敏规则配置在 Nacos 配置中心,支持热更新 无需重启服务,实时生效
    嵌套对象脱敏 复杂数据结构(如订单包含用户、地址)容易遗漏脱敏 在 AOP 切面中递归遍历所有字段,处理嵌套对象和集合 支持任意复杂的数据结构

    面试加分总结💯

    数据脱敏的核心不是某一个算法,而是全链路的防护体系。我们的方案做到了:

    • 业务代码零侵入:通过注解和 AOP 实现
    • 可扩展性强:新增脱敏算法只需实现一个接口
    • 动态灵活:支持基于角色的细粒度脱敏
    • 性能优秀:缓存策略实例,支持分布式批量处理

    最重要的是,我们建立了定期的脱敏效果审计机制,避免出现 "假脱敏" 的情况。

    真实面试模拟

    真实面试模拟

    👨‍💼 面试官:

    同学你好,今天场景设计题我们聊个实际的——你在项目中数据脱敏是怎么做的? 不用面面俱到,把你认为最关键的几层设计讲清楚就行。

    🧑‍💻 候选人:

    好的面试官。我们主要是按展示、日志、存储、导出四层来分别处理的,先给您看个总览图,心里有个谱:

    ┌─────────────────────────────────────────────────────────┐
    │                    数据脱敏体系                           │
    ├─────────────┬──────────────┬──────────────┬─────────────┤
    │  展示层      │   日志层      │  传输/存储层   │  数据导出层   │
    │  (动态脱敏)  │  (日志脱敏)   │  (静态脱敏)    │  (静态脱敏)   │
    ├─────────────┼──────────────┼──────────────┼─────────────┤
    │ 注解+AOP    │ logback       │ 数据库字段    │ EasyExcel    │
    │ Jackson序列化│ 自定义Converter│ 加密存储(AES) │ 脱敏注解     │
    │ 网关脱敏    │ 基于正则替换   │ 脱敏视图      │ 异步脱敏处理  │
    └─────────────┴──────────────┴──────────────┴─────────────┘
    

    👨‍💼 面试官:

    图很清晰 👍 先说说展示层动态脱敏吧,你们是怎么让前端看到的接口 JSON 里手机号直接变成 138****5678 的?

    🧑‍💻 候选人:

    这块核心用的是 Jackson 自定义序列化 + 注解,对业务代码侵入特别小。

    // ① 自定义脱敏注解
    @Retention(RetentionPolicy.RUNTIME)
    @JacksonAnnotationsInside
    @JsonSerialize(using = SensitiveSerialize.class)
    public @interface Sensitive {
        SensitiveType type(); // 枚举:MOBILE, ID_CARD, NAME...
    }
    // ② 自定义序列化器,根据type选择脱敏策略
    public class SensitiveSerialize extends JsonSerializer {
        @Override
        public void serialize(String value, JsonGenerator gen, SerializerProvider provider) {
            Sensitive anno = provider.getAttribute("sensitive_anno");
            gen.writeString(SensitiveStrategy.get(anno.type()).desensitize(value));
        }
    }
    

    VO 字段上加个注解就行:

    public class UserVO {
        @Sensitive(type = SensitiveType.MOBILE)
        private String mobile;
    }
    

    💡 注意点:高并发下别在策略里临时编译正则,提前缓存 Pattern,压测 QPS 影响不超过 2%。

    👨‍💼 面试官:

    如果系统里字段特别多,总不能一个个手加注解吧?

    🧑‍💻 候选人:

    对,所以我们还会做“敏感数据自动发现”,用正则扫描元数据打标,然后通过配置中心动态驱动序列化,做到新接口上线零改动。

    👨‍💼 面试官:

    好,那日志脱敏你们怎么防止把身份证打到 ELK 里?

    🧑‍💻 候选人:

    全局重写了 logback 的 MessageConverter,匹配敏感信息替换,开发无感知。

    public class SensitiveLogConverter extends MessageConverter {
        @Override
        public String convert(ILoggingEvent event) {
            String msg = super.convert(event);
            return SensitiveLogUtil.desensitize(msg); // 链式正则替换
        }
    }
    

    logback.xml 配置一行:

    <conversionRule conversionWord="msg" 
        converterClass="com.xxx.SensitiveLogConverter" />
    

    🛡️ 还把 MDC 里的 userId 做了哈希处理,这样就算日志泄露也关联不到真人。

    👨‍💼 面试官:

    日志层过了,那数据库存储这层怎么办?有些场景还得能还原对吧。

    🧑‍💻 候选人:

    对,实名数据需要可逆,我们用应用层透明加解密,MyBatis 的 TypeHandler 搞定:

    public class EncryptTypeHandler extends BaseTypeHandler {
        public void setNonNullParameter(PreparedStatement ps, int i, String param) {
            ps.setString(i, AESUtil.encrypt(param));
        }
        // 查询时自动解密…
    }
    

    密钥走 KMS,不落地,权限隔离。不可逆的查询条件用 HASH 关联,DBA 还会建脱敏视图给低权限应用。

    👨‍💼 面试官:

    那运营导出 Excel 这种场景呢?不同角色看到的数据还不一样。

    🧑‍💻 候选人:

    我们用策略工厂 + EasyExcel 拦截器动态处理。

    public class SensitiveStrategyFactory {
        public static SensitiveStrategy get(Role role, SensitiveType type) {
            if (role == Role.ADMIN) return NoOpStrategy.INSTANCE; // 管理员看明文
            return registry.get(type); // 普通运营按类型脱敏
        }
    }
    

    大数据量导出就异步任务:查询 → 逐行脱敏 → 上传 OSS → 通知下载,避免超时。

    👨‍💼 面试官:

    最后帮我总结下,这套设计的核心原则是什么?

    🧑‍💻 候选人:

    三个点:

    • 低侵入:能用自动化、配置驱动的绝不手写;
    • 分层治理:展示、日志、存储、导出各有专门方案;
    • 可追溯:脱敏操作留痕,能知道谁看了什么数据。

    这样既能过安全审计,开发体验也不差 😄

    👨‍💼 面试官:

    讲得很落地,没大问题。那你们落地过程中遇到过什么坑吗?

    🧑‍💻 候选人:

    比如日志脱敏刚开始把接口参数里的 JSON 手机号替换了,但没考虑到 MDC 里的原始值,导致链路追踪里漏了,后面补上了 MDC 过滤;还有动态脱敏注解在 Feign 调用时序列化不生效,需要把序列化器也注册到 Feign 的编解码器里。

    👨‍💼 面试官:

    刚才几层设计思路很清晰,那能不能把你觉得最有技术亮点的代码贴出来看看?另外,这个脱敏体系落地时,技术难点有哪些?你们是怎么解决的?

    🧑‍💻 候选人:

    没问题,我挑三段核心代码,然后说四个关键难点和方案。

    🔥 核心亮点代码

    1. 展示层:可缓存策略的序列化器(解决性能)

    public class SensitiveSerialize extends JsonSerializer 
                                      implements ContextualSerializer {
        // 缓存编译好的策略函数,避免每次序列化都查找
        private static final Map> STRATEGY_CACHE = 
            new ConcurrentHashMap<>();
    
        private SensitiveType type;
    
        @Override
        public void serialize(String value, JsonGenerator gen, 
                              SerializerProvider provider) throws IOException {
            if (value == null) {
                gen.writeNull();
                return;
            }
            Function func = STRATEGY_CACHE.computeIfAbsent(type,
                t -> SensitiveStrategyRegistry.get(t)); // 策略注册表
            gen.writeString(func.apply(value));
        }
    
        @Override
        public JsonSerializer createContextual(SerializerProvider prov,
                                                  BeanProperty property) {
            Sensitive anno = property.getAnnotation(Sensitive.class);
            if (anno != null) {
                SensitiveSerialize serializer = new SensitiveSerialize();
                serializer.type = anno.type();
                return serializer;
            }
            return this;
        }
    }
    

    💡 亮点:通过 ConcurrentHashMap 缓存策略函数,高并发下只做一次策略匹配,压测时序列化几乎无额外开销。

    2. 存储层:MyBatis 透明加解密 TypeHandler

    @MappedTypes(String.class)
    public class AESEncryptTypeHandler extends BaseTypeHandler {
        
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, 
                                       String param, JdbcType jdbcType) 
                                       throws SQLException {
            // 加密后入库,密钥从KMS获取的本地缓存取
            ps.setString(i, AESUtils.encrypt(param, KeyHolder.get()));
        }
    
        @Override
        public String getNullableResult(ResultSet rs, String columnName) 
                                        throws SQLException {
            String encrypted = rs.getString(columnName);
            return encrypted != null ? AESUtils.decrypt(encrypted, KeyHolder.get()) : null;
        }
        // 其他 getNullableResult 重载...
    }
    

    💡 亮点:结合 KeyHolder 内存缓存 KMS 密钥,避免每次加解密都远程调用;业务代码完全不用改,只需在 mapper.xml 指定 typeHandler。

    3. 敏感数据自动发现扫描器(解决字段打标难题)

    @Component
    public class SensitiveDataScanner {
        
        // 内置常见敏感列正则
        private static final Map RULES = Map.of(
            "mobile", Pattern.compile(".*(phone|mobile|tel).*", Pattern.CASE_INSENSITIVE),
            "id_card", Pattern.compile(".*(id_card|identity|身份证).*", Pattern.CASE_INSENSITIVE)
            // ... 更多规则
        );
    
        @Scheduled(cron = "0 0 3 * * ?") // 每天凌晨3点扫描
        public void scanAndTag() {
            List tables = metadataService.getAllTables();
            for (TableMetadata table : tables) {
                for (ColumnMeta col : table.getColumns()) {
                    SensitiveType type = matchType(col.getName(), col.getComment());
                    if (type != null) {
                        tagService.saveOrUpdate(table.getName(), col.getName(), type);
                    }
                }
            }
        }
    
        private SensitiveType matchType(String colName, String comment) {
            for (Map.Entry entry : RULES.entrySet()) {
                if (entry.getValue().matcher(colName).matches() ||
                    (comment != null && entry.getValue().matcher(comment).matches())) {
                    return SensitiveType.valueOf(entry.getKey().toUpperCase());
                }
            }
            return null;
        }
    }
    

    💡 亮点:用定时任务自动打标,新字段上线一天内自动纳入脱敏体系,彻底解决人工加注解遗漏的痛点。

    🧨 技术难点与解决方案

    难点 问题描述 解决方案
    性能开销 日志/序列化脱敏频繁正则匹配,拖慢接口 ① 策略函数缓存;② 正则 Pattern 预编译并缓存;③ 网关层统一脱敏分流
    注解跨服务失效 Feign/RPC 调用时 Jackson 脱敏序列化器不生效 让 API 返回的 VO 实现统一脱敏接口,在 Feign 编解码器里注入同一个序列化器
    密钥安全与轮换 数据库加密密钥硬编码、泄露风险、无法轮换 引入 KMS,密钥不落地,应用启动时拉到内存(定期刷新),按需加解密且权限最小化
    日志脱敏遗漏 只脱敏了日志体,MDC、异常堆栈中的敏感数据仍明文化 扩展 MessageConverter 同时处理格式化消息和 MDC 值;异常信息在全局异常处理器中二次脱敏

    难点解决流程图(简化)

    ┌─────────────┐      ┌──────────────────┐      ┌───────────────┐
    │ 数据请求到达  │────▶│ 网关层统一脱敏(可选) │────▶│ 业务服务       │
    └─────────────┘      └──────────────────┘      └───────┬───────┘
                                                            │
                             ┌───────────────────────────────┤
                             ▼                               ▼
                  ┌─────────────────┐            ┌──────────────────┐
                  │ Jackson序列化    │            │ 日志脱敏Converter │
                  │ (缓存策略+正则)  │            │ (全局+MDC)       │
                  └─────────────────┘            └──────────────────┘
                             │                               │
                             ▼                               ▼
                  ┌─────────────────┐            ┌──────────────────┐
                  │ 返回脱敏后JSON   │            │ 输出安全日志      │
                  └─────────────────┘            └──────────────────┘
    

    👨‍💼 面试官:

    很好,把细节和坑都讲清楚了。特别是性能缓存和自动发现这两个点,确实能看出实际落地的深度 👍 那今天数据脱敏这块我们就聊到这里。

    🧑‍💻 候选人:

    谢谢面试官 😄


    如果本文对你有帮助,欢迎关注我的公众号【Rain的Java大神之路】。
    专注 Java 面试、源码、高并发实战,回复“Java”领取《大厂面试手册》,持续更新。

  • 相关阅读:
    人工神经网络算法的应用,人工神经网络算法步骤
    什么是Jmeter ?Jmeter使用的原理步骤是什么?
    Java泛型
    实战:kubeadm方式搭建k8s集群(containerd)-2022.12.5(成功测试-超详细)【荐】
    服务器的初始化
    黑客(网络安全)技术自学——高效学习
    git刷新hosts
    运算符优先级
    wavesummit2024发布飞桨3.0版本
    NoSql的优势在哪里,NoSql是什么
  • 原文地址:https://www.cnblogs.com/zrui-xyu/p/23007299