后端接口的 “数据脱敏” 实践:在安全与可用性之间找平衡

254 阅读4分钟

在后端系统中,用户数据(如手机号、身份证号、银行卡号)的安全防护至关重要。数据脱敏技术通过对敏感信息进行部分隐藏(如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()方法也应用了脱敏逻辑

  • 合规性优先:根据法规要求调整脱敏策略(如身份证号脱敏需符合《个人信息保护法》)

数据脱敏不是简单的 “替换字符”,而是一套完整的安全体系。它需要在 “数据可用性” 和 “安全性” 之间找到平衡,既不让敏感数据裸奔,也不让业务人员因信息不全而无法工作,这是后端安全设计的 “精细活”。