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 实现流程
- 初始化:读取配置文件,构建白名单
Set、黑名单Set、正则List<Pattern>、路径规则Map<String, List<String>>。 - 拦截器入口:在
intercept()中捕获请求体/响应体(仅当Content-Type为application/json时)。 - JSON解析:使用
JSONObject或JsonElement(Gson/Jackson)将字符串转为树结构。 - 递归遍历:从根节点开始,对每个字段执行匹配顺序:白名单 → 黑名单 → 正则 → 路径匹配。
- 标记替换:匹配到的字段值替换为固定掩码(如
"***"),保留字段名与类型。 - 重新序列化:将修改后的树结构转回字符串,放回请求体/响应体。
注意:不要修改原始字段的类型(如将数字改为字符串),否则可能导致下游解析崩溃。建议统一替换为
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 规则设计流程总图
3.10 部署注意事项
- 规则配置文件随APK打包(资源目录
assets),支持应用内加载;若需动态更新,建议通过远程配置(Firebase Remote Config或自建配置中心)。 - 在OkHttp拦截器链中,隐私过滤拦截器应放置在最外层(最后添加),避免影响其他拦截器(如重试、日志)读取原始数据。
- 若App包含多个BaseUrl,规则可为每个BaseUrl独立配置,通过拦截器内的
request.url()判断。
部署检查清单:① 规则文件是否可读?② 正则是否会引起StackOverflow?③ 白名单是否遗漏了业务必须保留的字段?④ 路径匹配是否正确处理了数组?(建议使用Gson的
JsonTreeReader进行递归)。