3. 隐私字段识别规则设计:基于正则表达式、字段名白名单/黑名单、嵌套JSON路径匹配

3.1 规则体系设计原理

隐私字段过滤的核心在于“识别”而非“猜测”。OkHttp拦截器需要一套多层级、可组合的匹配规则,才能准确区分敏感字段(如密码、身份证)与普通字段(如用户名、备注)。规则设计遵循三层递进:

  • 字段名白名单/黑名单:最高优先级,明确标记“必须过滤”或“绝不过滤”。
  • 正则表达式:模糊匹配字段名模式(如 password.*、),适应动态字段命名。
  • 嵌套JSON路径匹配:精确控制深层嵌套字段(如 $.data.user.creditCard.number)。

规则引擎以短路优先级运行:白名单 > 黑名单 > 正则 > 路径匹配。一旦白名单命中,直接标记为隐私;黑名单命中则跳过;余下字段再依次执行正则与路径匹配。

设计目标:配置文件可热加载,不要求修改代码即可动态调整过滤范围。规则变更仅需重启拦截器(或通过OkHttp的Dispatcher调度新拦截器链)。

3.2 规则数据结构与配置格式

推荐使用YAML或JSON格式存储规则,拦截器启动时加载。以下为YAML示例:

# privacy-rules.yaml
whitelist:
  - "username"
  - "nickName"
  - "avatarUrl"
blacklist:
  - "password"
  - "secret"
  - "creditCard"
regex:
  - pattern: ".*"
    description: "所有以开头的字段"
  - pattern: "(?i)auth.*"
    description: "不区分大小写的auth前缀"
pathMatching:
  - path: "$.user.identity"
    fields:
      - "idNumber"
      - "passportNo"
  - path: "$.payment.*"
    fields:
      - "cvv"
      - "expiry"

拦截器内部用Map<String, List<String>>组织路径规则,正则部分编译为Pattern对象缓存,避免每次匹配都重新编译。

3.3 实现流程

  1. 初始化:读取配置文件,构建白名单Set、黑名单Set、正则List<Pattern>、路径规则Map<String, List<String>>。
  2. 拦截器入口:在intercept()中捕获请求体/响应体(仅当Content-Type为application/json时)。
  3. JSON解析:使用JSONObject或JsonElement(Gson/Jackson)将字符串转为树结构。
  4. 递归遍历:从根节点开始,对每个字段执行匹配顺序:白名单 → 黑名单 → 正则 → 路径匹配。
  5. 标记替换:匹配到的字段值替换为固定掩码(如"***"),保留字段名与类型。
  6. 重新序列化:将修改后的树结构转回字符串,放回请求体/响应体。
注意:不要修改原始字段的类型(如将数字改为字符串),否则可能导致下游解析崩溃。建议统一替换为String类型的掩码,但保持JSON格式合法。

3.4 核心代码片段(Kotlin)

class PrivacyFieldMatcher(
    private val yamlConfig: Config
) {
    private val whitelist: Set<String>
    private val blacklist: Set<String>
    private val patterns: List<Pair<Pattern, String>>
    private val pathRules: Map<String, List<String>>

    init {
        whitelist = yamlConfig.getStringList("whitelist").toSet()
        blacklist = yamlConfig.getStringList("blacklist").toSet()
        patterns = yamlConfig.getConfigList("regex").map {
            Pattern.compile(it.getString("pattern")) to it.getString("description")
        }
        pathRules = yamlConfig.getConfigList("pathMatching").associate { rule ->
            rule.getString("path") to rule.getStringList("fields")
        }
    }

    fun isSensitive(fieldName: String, currentPath: String): Boolean {
        // 1. 白名单优先级最高
        if (fieldName in whitelist) return false
        // 2. 黑名单
        if (fieldName in blacklist) return true
        // 3. 正则匹配
        for ((pattern, _) in patterns) {
            if (pattern.matcher(fieldName).matches()) return true
        }
        // 4. 路径匹配
        for ((path, fields) in pathRules) {
            if (currentPath.startsWith(path.replace("*", "")) && 
                fieldName in fields) return true
        }
        return false
    }
}

3.5 嵌套JSON路径匹配实现

路径匹配使用点分路径表示法,支持通配符*表示任意层级。递归遍历时维护当前路径字符串(如$.data.user.creditCard)。判断逻辑:

  • 将规则路径中的*视为“匹配任意单段”。
  • 将当前路径按.分割为段列表,与规则路径段逐一比较。
  • 若规则段为*,则跳过;若规则段为具体字段名,则必须精确匹配。
  • 当路径完全匹配且当前字段名在规则指定的fields列表中,标记为隐私。
fun isPathMatch(currentPath: String, rulePath: String, fieldName: String, fields: List<String>): Boolean {
    val currentSegments = currentPath.split(".").drop(1) // 忽略"$"
    val ruleSegments = rulePath.split(".").drop(1)
    if (currentSegments.size != ruleSegments.size) return false
    for (i in ruleSegments.indices) {
        if (ruleSegments[i] != "*" && ruleSegments[i] != currentSegments[i]) return false
    }
    return fieldName in fields
}

3.6 调试与验证

  • 单元测试:针对每类规则编写JUnit测试,覆盖白名单、黑名单、正则命中、路径匹配等场景。
  • 日志输出:在正式拦截器之前添加一个调试拦截器,打印“字段名 → 是否敏感 → 匹配规则类型”。
  • 模拟数据:准备典型JSON样本(如用户注册、支付请求),验证密码、电话、身份证被替换,而用户名、备注保留。
调试技巧:在OkHttp的EventListener中监听responseBodyStart事件,打印原始与过滤后的body长度,快速判断过滤是否生效。

3.7 常见错误与避坑

错误场景原因解决方案
正则表达式导致OOM未限制递归或使用了回溯陷阱(如(.*)*)正则只用于字段名(短字符串),且限制长度;使用Matcher.matches()而非find()
路径匹配失效JSON嵌套数组未展开导致路径不连续遍历时对数组元素递归并在路径后追加[index](如$.items[0].name)
白名单优先级被覆盖误将白名单放在正则之后执行严格按白名单→黑名单→正则→路径的顺序
大小写敏感导致漏匹配字段名混用camelCase与PascalCase正则开头加(?i),或统一转为小写再匹配

3.8 性能与稳定性优化

  • 正则预编译:初始化时将所有正则Pattern.compile(),避免每次判断都编译。
  • 白名单/黑名单使用HashSet:查找O(1)。
  • 路径匹配缓存:对已计算过的字段路径结果进行缓存(Map<String, Boolean>),相同路径的JSON结构重复出现时直接命中。
  • 大JSON流式处理:若响应体超过1MB,改用JsonReader流式遍历,递归替换时只修改匹配字段的值,不构建完整树结构。
  • 线程安全:规则集合初始化后不可变,多线程共享无需同步;若支持热加载,使用AtomicReference交换配置。
建议:将规则配置放在文件系统或远端配置中心,拦截器启动时加载并定时刷新(如每5分钟)。刷新期间使用Copy-on-Write策略,避免读写冲突。

3.9 规则设计流程总图

隐私字段识别规则设计流程 v1.0 输入:原始JSON字符串 JSON解析为树结构 递归遍历每个字段 白名单匹配 黑名单匹配 正则匹配 路径匹配 替换并重序列化输出 规则优先级:白名单 > 黑名单 > 正则 > 路径匹配,命中即标记;路径匹配支持通配符 *

3.10 部署注意事项

  • 规则配置文件随APK打包(资源目录assets),支持应用内加载;若需动态更新,建议通过远程配置(Firebase Remote Config或自建配置中心)。
  • 在OkHttp拦截器链中,隐私过滤拦截器应放置在最外层(最后添加),避免影响其他拦截器(如重试、日志)读取原始数据。
  • 若App包含多个BaseUrl,规则可为每个BaseUrl独立配置,通过拦截器内的request.url()判断。
部署检查清单:① 规则文件是否可读?② 正则是否会引起StackOverflow?③ 白名单是否遗漏了业务必须保留的字段?④ 路径匹配是否正确处理了数组?(建议使用Gson的JsonTreeReader进行递归)。