本文目录导读:

我来介绍几种常见的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);
}
}
推荐实践建议
- 使用AOP统一处理:在服务层或控制层统一进行脱敏,避免重复代码
- 区分脱敏场景:日志脱敏、前端展示脱敏、API响应脱敏可以用不同策略
- 数据权限控制:普通用户和管理员可以看到不同层级的脱敏数据
- 性能优化:使用缓存,避免每次查询都进行脱敏计算
方案可以根据实际项目需求灵活选择或组合使用。