Java数据脱敏案例如何编写

wen java案例 22

本文目录导读:

Java数据脱敏案例如何编写

  1. 基础字符串脱敏实现
  2. 基于注解的AOP脱敏(推荐)
  3. JSON序列化脱敏
  4. 使用Jackson的脱敏工具类
  5. 数据库查询时脱敏(MyBatis示例)
  6. 完整的使用示例
  7. 推荐实践建议

我来介绍几种常见的Java数据脱敏案例和实现方法。

基础字符串脱敏实现

public class DataMaskUtil {
    /**
     * 手机号脱敏:138****1234
     */
    public static String maskPhone(String phone) {
        if (phone == null || phone.length() != 11) {
            return phone;
        }
        return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    }
    /**
     * 身份证号脱敏:110101****1234
     */
    public static String maskIdCard(String idCard) {
        if (idCard == null || idCard.length() < 10) {
            return idCard;
        }
        return idCard.replaceAll("(\\d{6})\\d{8,10}(\\w{4})", "$1********$2");
    }
    /**
     * 邮箱脱敏:tes***@example.com
     */
    public static String maskEmail(String email) {
        if (email == null || !email.contains("@")) {
            return email;
        }
        String[] parts = email.split("@");
        String name = parts[0];
        int length = name.length();
        if (length <= 3) {
            return name.substring(0, 1) + "***@" + parts[1];
        }
        return name.substring(0, 3) + "***@" + parts[1];
    }
    /**
     * 姓名脱敏:张*或张**(保留姓)
     */
    public static String maskName(String name) {
        if (name == null || name.length() <= 1) {
            return name;
        }
        String firstChar = name.substring(0, 1);
        String mask = String.join("", Collections.nCopies(name.length() - 1, "*"));
        return firstChar + mask;
    }
    /**
     * 银行卡号脱敏:6222****1234
     */
    public static String maskBankCard(String cardNo) {
        if (cardNo == null || cardNo.length() < 8) {
            return cardNo;
        }
        return cardNo.replaceAll("(\\d{4})\\d{8,12}(\\d{4})", "$1********$2");
    }
    /**
     * 自定义脱敏
     * @param str 原始字符串
     * @param start 开始保留位数
     * @param end 结尾保留位数
     * @param maskChar 掩码字符
     */
    public static String maskCustom(String str, int start, int end, char maskChar) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        if (start + end >= str.length()) {
            return str;
        }
        StringBuilder sb = new StringBuilder(str);
        for (int i = start; i < str.length() - end; i++) {
            sb.setCharAt(i, maskChar);
        }
        return sb.toString();
    }
}

基于注解的AOP脱敏(推荐)

1 定义脱敏注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveField {
    SensitiveType value() default SensitiveType.DEFAULT;
}
public enum SensitiveType {
    PHONE,       // 手机号
    ID_CARD,     // 身份证
    EMAIL,       // 邮箱
    NAME,        // 姓名
    BANK_CARD,   // 银行卡
    ADDRESS,     // 地址
    DEFAULT      // 默认脱敏
}

2 实体类使用注解

public class User {
    private Long id;
    @SensitiveField(SensitiveType.NAME)
    private String name;
    @SensitiveField(SensitiveType.PHONE)
    private String phone;
    @SensitiveField(SensitiveType.ID_CARD)
    private String idCard;
    @SensitiveField(SensitiveType.EMAIL)
    private String email;
    @SensitiveField(SensitiveType.BANK_CARD)
    private String bankCard;
    // getter/setter方法
}

3 AOP实现脱敏处理

@Aspect
@Component
public class SensitiveDataAspect {
    @Around("@annotation(com.example.annotation.MaskData)")
    public Object maskSensitiveData(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = joinPoint.proceed();
        if (result instanceof Collection) {
            Collection<?> collection = (Collection<?>) result;
            collection.forEach(this::maskObject);
        } else if (result instanceof Page) {
            Page<?> page = (Page<?>) result;
            page.getContent().forEach(this::maskObject);
        } else if (result != null) {
            maskObject(result);
        }
        return result;
    }
    private void maskObject(Object obj) {
        if (obj == null) return;
        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            SensitiveField annotation = field.getAnnotation(SensitiveField.class);
            if (annotation != null) {
                field.setAccessible(true);
                try {
                    Object value = field.get(obj);
                    if (value instanceof String && !((String) value).isEmpty()) {
                        String maskedValue = maskByType((String) value, annotation.value());
                        field.set(obj, maskedValue);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    private String maskByType(String value, SensitiveType type) {
        switch (type) {
            case PHONE:
                return DataMaskUtil.maskPhone(value);
            case ID_CARD:
                return DataMaskUtil.maskIdCard(value);
            case EMAIL:
                return DataMaskUtil.maskEmail(value);
            case NAME:
                return DataMaskUtil.maskName(value);
            case BANK_CARD:
                return DataMaskUtil.maskBankCard(value);
            default:
                return value;
        }
    }
}

4 自定义脱敏注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MaskData {
    String description() default "";
}

JSON序列化脱敏

1 自定义序列化器

public class SensitiveInfoSerializer extends JsonSerializer<String> {
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        String maskedValue = maskSensitiveInfo(value);
        gen.writeString(maskedValue);
    }
    private String maskSensitiveInfo(String value) {
        // 根据实际需求实现脱敏逻辑
        if (value.matches("^1[3-9]\\d{9}$")) {
            return DataMaskUtil.maskPhone(value);
        }
        return value;
    }
}

2 实体类使用序列化器

public class UserVO {
    private Long id;
    @JsonSerialize(using = SensitiveInfoSerializer.class)
    private String name;
    @JsonSerialize(using = SensitiveInfoSerializer.class)
    private String phone;
    // 其他字段
}

使用Jackson的脱敏工具类

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
public class JsonMaskUtil {
    private static final ObjectMapper mapper = new ObjectMapper();
    static {
        SimpleModule module = new SimpleModule();
        module.addSerializer(String.class, new JsonSerializer<String>() {
            @Override
            public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) 
                    throws IOException {
                String maskedValue = applyMask(value);
                gen.writeString(maskedValue);
            }
            private String applyMask(String value) {
                // 根据正则判断不同类型并脱敏
                if (value.matches("^1[3-9]\\d{9}$")) {
                    return DataMaskUtil.maskPhone(value);
                }
                // 其他脱敏规则...
                return value;
            }
        });
        mapper.registerModule(module);
    }
    public static String toMaskedJson(Object obj) throws Exception {
        return mapper.writeValueAsString(obj);
    }
}

数据库查询时脱敏(MyBatis示例)

@Mapper
public interface UserMapper {
    // 方式1:SQL层脱敏
    @Select("SELECT id, " +
            "CONCAT(LEFT(name, 1), '****', RIGHT(name, LENGTH(name) - LENGTH(LEFT(name, 1)))) as name, " +
            "CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) as phone " +
            "FROM user WHERE id = #{id}")
    UserVO findByIdWithMask(@Param("id") Long id);
    // 方式2:使用自定义TypeHandler
    @Select("SELECT * FROM user WHERE id = #{id}")
    @Results({
        @Result(property = "phone", column = "phone", typeHandler = PhoneTypeHandler.class)
    })
    UserVO findByIdWithHandler(@Param("id") Long id);
}
// 自定义TypeHandler
public class PhoneTypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) 
            throws SQLException {
        ps.setString(i, parameter);
    }
    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String value = rs.getString(columnName);
        return DataMaskUtil.maskPhone(value);
    }
    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String value = rs.getString(columnIndex);
        return DataMaskUtil.maskPhone(value);
    }
    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String value = cs.getString(columnIndex);
        return DataMaskUtil.maskPhone(value);
    }
}

完整的使用示例

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    @MaskData  // AOP自动脱敏
    public List<UserVO> getUsers() {
        return userMapper.findAll();
    }
    // 手动脱敏
    public UserVO getUserWithMask(Long id) {
        UserVO user = userMapper.findById(id);
        user.setPhone(DataMaskUtil.maskPhone(user.getPhone()));
        user.setName(DataMaskUtil.maskName(user.getName()));
        return user;
    }
    // JSON序列化脱敏
    public String getUserJson(Long id) throws Exception {
        UserVO user = userMapper.findById(id);
        return JsonMaskUtil.toMaskedJson(user);
    }
}

推荐实践建议

  1. 使用AOP统一处理:在服务层或控制层统一进行脱敏,避免重复代码
  2. 区分脱敏场景:日志脱敏、前端展示脱敏、API响应脱敏可以用不同策略
  3. 数据权限控制:普通用户和管理员可以看到不同层级的脱敏数据
  4. 性能优化:使用缓存,避免每次查询都进行脱敏计算

方案可以根据实际项目需求灵活选择或组合使用。

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