Java案例如何实现数据脱敏?

wen java案例 5

本文目录导读:

Java案例如何实现数据脱敏?

  1. 工具类方法(最基础,手动调用)
  2. 基于注解 + AOP(推荐,侵入性低)
  3. MyBatis 插件方式(数据库层面拦截)
  4. 日志脱敏(配合 Logback / Log4j)
  5. 总结:如何选择?

在Java中实现数据脱敏,通常有几种常见的方法:字符串替换、注解+AOP、MyBatis插件、以及JSON序列化拦截,以下是几种主流且实用的实现方案及其代码示例。


工具类方法(最基础,手动调用)

适用于简单场景,如日志打印、手动处理单个字段。

public class DesensitizedUtil {
    /**
     * 手机号脱敏:保留前3后4
     */
    public static String mobile(String phone) {
        if (phone == null || phone.length() != 11) return phone;
        return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    }
    /**
     * 身份证号脱敏:保留前6后4
     */
    public static String idCard(String id) {
        if (id == null || id.length() < 10) return id;
        return id.replaceAll("(\\d{6})\\d{8,12}(\\d{4})", "$1********$2");
    }
    /**
     * 邮箱脱敏:用户名部分显示首字母+***
     */
    public static String email(String email) {
        if (email == null || !email.contains("@")) return email;
        String name = email.substring(0, email.indexOf("@"));
        String domain = email.substring(email.indexOf("@"));
        return name.charAt(0) + "***" + domain;
    }
    /**
     * 姓名脱敏:显示姓氏,名字用*代替(中文)
     */
    public static String name(String name) {
        if (name == null || name.length() <= 1) return name;
        if (name.length() == 2) {
            return name.charAt(0) + "*";
        }
        return name.charAt(0) + "*" + name.substring(name.length() - 1);
    }
}

使用示例:

System.out.println(DesensitizedUtil.mobile("13812345678")); // 138****5678

基于注解 + AOP(推荐,侵入性低)

适合全局脱敏,尤其是返回给前端的JSON数据。

步骤:

  1. 定义脱敏枚举和注解
  2. 使用 Jackson 自定义序列化器
  3. 在字段上加注解

1 定义脱敏类型枚举

public enum DesensitizedType {
    MOBILE,        // 手机号
    ID_CARD,       // 身份证
    EMAIL,         // 邮箱
    NAME           // 姓名
}

2 自定义注解

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizedSerializer.class)
public @interface Sensitive {
    DesensitizedType value();
}

3 自定义 Jackson 序列化器

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class DesensitizedSerializer extends JsonSerializer<String> {
    private DesensitizedType type;
    public DesensitizedSerializer(DesensitizedType type) {
        this.type = type;
    }
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        switch (type) {
            case MOBILE:
                gen.writeString(DesensitizedUtil.mobile(value));
                break;
            case ID_CARD:
                gen.writeString(DesensitizedUtil.idCard(value));
                break;
            case EMAIL:
                gen.writeString(DesensitizedUtil.email(value));
                break;
            case NAME:
                gen.writeString(DesensitizedUtil.name(value));
                break;
            default:
                gen.writeString(value);
        }
    }
}

4 在实体类中使用

public class UserDTO {
    @Sensitive(DesensitizedType.NAME)
    private String name;
    @Sensitive(DesensitizedType.MOBILE)
    private String phone;
    @Sensitive(DesensitizedType.EMAIL)
    private String email;
    // getter/setter
}

这样,当对象通过 Jackson 序列化返回给前端时,会自动脱敏。


MyBatis 插件方式(数据库层面拦截)

适用于从数据库查询出来后自动脱敏,对业务代码完全无侵入。

1 自定义注解(用在实体字段上)

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveField {
    DesensitizedType value();
}

2 MyBatis 拦截器(ResultSetHandler)

import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.*;
@Intercepts({
    @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class DesensitizationInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        List<Object> results = (List<Object>) invocation.proceed();
        for (Object result : results) {
            if (result != null) {
                process(result);
            }
        }
        return results;
    }
    private void process(Object obj) throws IllegalAccessException {
        Class<?> clazz = obj.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(SensitiveField.class)) {
                SensitiveField annotation = field.getAnnotation(SensitiveField.class);
                field.setAccessible(true);
                String value = (String) field.get(obj);
                if (value != null) {
                    String desensitized = doDesensitize(value, annotation.value());
                    field.set(obj, desensitized);
                }
            }
        }
    }
    private String doDesensitize(String value, DesensitizedType type) {
        switch (type) {
            case MOBILE: return DesensitizedUtil.mobile(value);
            case ID_CARD: return DesensitizedUtil.idCard(value);
            case EMAIL: return DesensitizedUtil.email(value);
            case NAME: return DesensitizedUtil.name(value);
            default: return value;
        }
    }
}

注册插件(Spring Boot + MyBatis配置):

@Configuration
public class MyBatisConfig {
    @Bean
    public Interceptor desensitizationInterceptor() {
        return new DesensitizationInterceptor();
    }
}

实体类使用:

public class User {
    @SensitiveField(DesensitizedType.NAME)
    private String name;
    @SensitiveField(DesensitizedType.MOBILE)
    private String phone;
}

日志脱敏(配合 Logback / Log4j)

使用 MessageConverter 或自定义 Layout,对日志中的敏感信息进行替换。

示例(基于正则替换,Logback)

public class SensitiveDataConverter extends MessageConverter {
    @Override
    public String convert(ILoggingEvent event) {
        String msg = event.getFormattedMessage();
        // 手机号:3-4-4格式
        msg = msg.replaceAll("(1[3-9]\\d)\\d{4}(\\d{4})", "$1****$2");
        // 身份证:18位
        msg = msg.replaceAll("(\\d{6})\\d{8,12}(\\d{4})", "$1********$2");
        return msg;
    }
}

logback.xml配置:

<conversionRule conversionWord="msg" converterClass="com.example.SensitiveDataConverter"/>

如何选择?

场景 推荐方案
简单的手动处理(打印/日志) 工具类
后端返回给前端的JSON自动脱敏 注解+Jackson序列化
数据库查询后全局脱敏(无侵入) MyBatis插件
日志脱敏 Logback/Layout转换器
非对称脱敏(保留可逆) AES加密 + 脱敏规则组合(更安全)

如果你的项目是 Spring Boot + Jackson(绝大多数),方案2(注解+Jackson序列化) 是最优雅、最常用的方式。

抱歉,评论功能暂时关闭!