如何对数据库中的JSON字段进行脱敏?

wen IT资讯 244

本文目录导读:

如何对数据库中的JSON字段进行脱敏?

  1. 方案一:数据库原生 JSON 函数 + 视图(推荐用于查询脱敏)
  2. 方案二:应用层编码(灵活可控,推荐用于存储脱敏)
  3. 方案三:数据库触发器(适用于自动写入脱敏)
  4. 方案四:数据虚拟化 / 中间件(适用于无侵入脱敏)
  5. 必须避开的 “坑”
  6. 总结推荐路径

对数据库中 JSON 字段进行脱敏,比处理普通字段更复杂,因为需要定位到 JSON 内部的特定键,而不是直接处理整个字段值。

具体采用哪种方法,主要取决于你的数据库类型(MySQL, PostgreSQL, MongoDB等)、脱敏触发时机(查询时实时脱敏 vs 存储前持久化脱敏)以及性能要求

以下是几种主流、可行的方案,按推荐程度和场景排序:

数据库原生 JSON 函数 + 视图(推荐用于查询脱敏)

这是性能最好、最安全的方案,通过创建数据库视图(View),在查询时动态替换敏感字段,不修改原始数据。

适用场景: MySQL 5.7+, PostgreSQL, 数据量较大,需要实时脱敏。

  • MySQL 示例: 假设表 users 中有一个 profile JSON 列,内容为 {"name": "张三", "idcard": "110101199001011234", "phone": "13800138000"}。 你需要对 idcardphone 脱敏。

    -- 创建视图,在查询时动态替换
    CREATE VIEW users_safe AS
    SELECT 
        id,
        JSON_SET(
            profile,
            '$.idcard', CONCAT(LEFT(profile->>'$.idcard', 4), '********'),
            '$.phone', CONCAT(LEFT(profile->>'$.phone', 3), '****', RIGHT(profile->>'$.phone', 4))
        ) AS profile
    FROM users;
  • PostgreSQL 示例: 使用 jsonb_set 函数,语法更简洁。

    CREATE VIEW users_safe AS
    SELECT 
        id,
        jsonb_set(
            jsonb_set(profile, '{idcard}', to_jsonb(substr(profile->>'idcard', 1, 4) || '******')),
            '{phone}', to_jsonb(substr(profile->>'phone', 1, 3) || '****' || substr(profile->>'phone', 8, 4))
        ) AS profile
    FROM users;

应用层编码(灵活可控,推荐用于存储脱敏)

在数据写入数据库前,在应用的后端代码中进行脱敏处理,这是最灵活的方式,可以调用复杂的脱敏算法(如保留格式、哈希等)。

适用场景: 微服务架构,数据合规要求高(如需要保留原始数据的同时存储脱敏副本)。

  • Python 示例:

    import json
    import re
    def mask_json_field(json_str: str, fields_to_mask: dict) -> str:
        """
        对 JSON 字符串中的指定字段进行脱敏
        :param json_str: 原始 JSON 字符串
        :param fields_to_mask: 字典,键为字段路径,值为脱敏函数
        :return: 脱敏后的 JSON 字符串
        """
        data = json.loads(json_str)
        for field, mask_func in fields_to_mask.items():
            if field in data:
                data[field] = mask_func(data[field])
        return json.dumps(data, ensure_ascii=False)
    # 使用示例
    def mask_phone(phone):
        return phone[:3] + '****' + phone[-4:]
    def mask_idcard(idcard):
        return idcard[:4] + '********' + idcard[-4:]
    raw_json = '{"name":"张三", "idcard":"110101199001011234", "phone":"13800138000"}'
    masked_json = mask_json_field(raw_json, {"phone": mask_phone, "idcard": mask_idcard})
    print(masked_json)
    # 输出: {"name": "张三", "idcard": "110101********1234", "phone": "138****8000"}

数据库触发器(适用于自动写入脱敏)

如果不想改应用代码,可以使用数据库的触发器(Trigger),在数据被 INSERTUPDATE 时,自动将原始 JSON 字段修改为脱敏后的值。

适用场景: 老旧系统无法改代码,但需要确保数据入库即脱敏。

  • MySQL 示例:

    DELIMITER //
    CREATE TRIGGER mask_json_before_insert 
    BEFORE INSERT ON users
    FOR EACH ROW
    BEGIN
        SET NEW.profile = JSON_SET(
            NEW.profile,
            '$.phone', CONCAT(LEFT(NEW.profile->>'$.phone', 3), '****', RIGHT(NEW.profile->>'$.phone', 4))
        );
    END;//
    DELIMITER ;

    注意: 触发器会降低写入性能,且一旦业务逻辑变更(如增加新字段),需要同步修改触发器。

数据虚拟化 / 中间件(适用于无侵入脱敏)

使用数据库代理(如 ProxySQL, MaxScale)或数据脱敏网关。

适用场景: 多应用共享数据库,不方便改动任何代码。

  • 原理: 代理工具解析 SQL 语句,识别到对 JSON 字段的查询后,在返回结果前注入脱敏逻辑,ProxySQL 可以通过 mysql_query_rules 匹配特定查询并改写 SQL,或使用外部的脱敏脚本。
  • 缺点: 配置复杂,且对 JSON 内部字段的深度解析支持有限,通常只能做正则替换。

必须避开的 “坑”

  1. 不要直接用 REPLACE 函数: UPDATE users SET profile = REPLACE(profile, '138', '***'),这会把 JSON 中所有出现 138 的地方(包括 key 或非手机号的值)都替换掉,直接破坏 JSON 结构。
  2. 不要忽略嵌套结构: JSON 可能是多层嵌套的({"contact":{"phone":"...", "email":"..."}}),使用 profile->'$.contact.phone' 这种方式才能准确定位。
  3. 保持字段长度一致性: 手机号从 13800138000 变成 138****8000,长度不变,这样不会影响数据库索引(如果是虚拟列索引)和下游系统的字段校验。
  4. 考虑性能索引: 如果经常对 JSON 内部字段(如 phone)做脱敏后查询,可以考虑为该字段创建虚拟生成列(Generated Column)并建立索引,避免每次查询都全量解析 JSON。

总结推荐路径

场景 推荐方案
数据量巨大,追求极致性能 数据库视图(查询脱敏)
合规严格,需要保存原始数据 + 脱敏数据 应用层编码(写入前脱敏,两份数据)
无法改代码,但能改数据库 触发器(写入时自动脱敏,注意性能)
需要动态、灵活的脱敏规则 应用层编码

最常见的最佳实践是 “方案二 + 方案一” 的组合:数据写入时,在应用层对敏感字段进行加密或脱敏后存储(避免明文落入日志);查询时,再通过数据库视图或其他手段进行二次脱敏,确保 API 返回给前端的数据是干净的。

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