在后端系统中,用户数据(如手机号、身份证号、银行卡号)的安全防护至关重要。数据脱敏技术通过对敏感信息进行部分隐藏(如138****5678),既能满足业务查看需求,又能防止敏感数据泄露,是合规性(如 GDPR、个人信息保护法)的核心要求。
脱敏的核心原则
数据脱敏需遵循 “最小够用” 和 “场景适配” 原则:
- 最小够用:只展示必要信息(如客服只需看到手机号前 3 后 4 位,无需完整号码)
- 场景适配:同一字段在不同场景脱敏规则不同(如日志中完全隐藏,页面展示部分隐藏)
常见脱敏场景与实现
1. 用户信息脱敏(手机号、身份证、银行卡)
手机号:保留前 3 位和后 4 位,中间用*代替(如138****5678)
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");
}
身份证号:保留前 6 位和后 4 位,中间用*代替(如110101********1234)
public static String maskIdCard(String idCard) {
if (idCard == null || (idCard.length() != 15 && idCard.length() != 18)) {
return idCard;
}
return idCard.replaceAll("(\d{6})\d+(\d{4})", "$1********$2");
}
银行卡号:保留最后 4 位,前面用*代替(如**** **** **** 1234)
public static String maskBankCard(String bankCard) {
if (bankCard == null || bankCard.length() < 16) {
return bankCard;
}
// 先去除空格
String card = bankCard.replaceAll(" ", "");
// 保留最后4位
String last4 = card.substring(card.length() - 4);
// 前面补*,并格式化
return String.format("**** **** **** %s", last4);
}
2. 日志脱敏:防止敏感信息写入日志
通过 AOP 拦截日志输出,对日志中的敏感字段自动脱敏:
@Aspect
@Component
public class LogMaskAspect {
// 定义需要脱敏的字段(手机号、身份证、银行卡)
private static final List<String> SENSITIVE_FIELDS = Arrays.asList("phone", "idCard", "bankCard");
@Around("execution(* org.slf4j.Logger.*(..))")
public Object maskLog(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {
return joinPoint.proceed();
}
// 对日志参数进行脱敏处理
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof String) {
args[i] = maskSensitive((String) args[i]);
}
}
return joinPoint.proceed(args);
}
// 对字符串中的敏感信息脱敏
private String maskSensitive(String content) {
for (String field : SENSITIVE_FIELDS) {
// 匹配类似 "phone:13812345678" 的格式
content = content.replaceAll(field + ":(\d{3})\d{4}(\d{4})", field + ":$1****$2");
// 身份证号匹配
content = content.replaceAll(field + ":(\d{6})\d+(\d{4})", field + ":$1********$2");
}
return content;
}
}
3. 数据库脱敏:存储与查询时的防护
使用 MyBatis 拦截器,在数据入库和查询时自动脱敏:
// 入库时脱敏(如手机号)
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class InsertMaskInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
BoundSql boundSql = statementHandler.getBoundSql();
Object parameterObject = boundSql.getParameterObject();
// 对参数中的手机号字段脱敏后入库
if (parameterObject instanceof User) {
User user = (User) parameterObject;
if (user.getPhone() != null) {
user.setPhone(maskPhone(user.getPhone()));
}
}
return invocation.proceed();
}
}
脱敏的进阶技巧
1. 动态脱敏:基于用户权限
不同角色看到的脱敏程度不同(如管理员看到完整信息,普通用户看到脱敏信息):
public String getPhoneByRole(User user, String role) {
String rawPhone = user.getPhone();
if ("ADMIN".equals(role)) {
return rawPhone; // 管理员可见完整号码
} else if ("CUSTOMER_SERVICE".equals(role)) {
return maskPhone(rawPhone); // 客服可见部分号码
} else {
return "***"; // 普通用户不可见
}
}
2. 可逆脱敏:特殊场景的临时查看
对需要临时查看完整信息的场景(如用户本人验证后),使用可逆加密:
// 加密(入库时)
public static String encrypt(String content, String key) {
// 使用AES加密,密钥由系统保管
return AESUtils.encrypt(content, key);
}
// 解密(验证后)
public static String decrypt(String encryptedContent, String key) {
// 验证用户身份后解密
return AESUtils.decrypt(encryptedContent, key);
}
避坑指南
-
避免过度脱敏:如将用户名脱敏为
***,可能影响业务操作(需结合实际场景) -
脱敏规则统一:前后端、日志、数据库的脱敏规则需一致,避免混淆
-
防止脱敏失效:日志打印对象时,需确保
toString()方法也应用了脱敏逻辑 -
合规性优先:根据法规要求调整脱敏策略(如身份证号脱敏需符合《个人信息保护法》)
数据脱敏不是简单的 “替换字符”,而是一套完整的安全体系。它需要在 “数据可用性” 和 “安全性” 之间找到平衡,既不让敏感数据裸奔,也不让业务人员因信息不全而无法工作,这是后端安全设计的 “精细活”。