apache响应体脱敏必须用mod_substitute,因mod_headers仅操作头、mod_proxy不处理响应体;需启用mod_filter和mod_substitute,显式声明mime类型,正确转义正则与分隔符,并注意编码、缓冲及调试日志影响。

Apache响应体脱敏必须用mod_substitute,mod_proxy或mod_headers都做不到
Apache本身不提供响应体内容改写能力,mod_headers只能操作头,mod_proxy不处理响应体文本。真正能对HTTP响应正文做正则替换的,只有mod_substitute——但它默认不启用,且依赖mod_filter和mod_charset_lite(后者在2.4.12+已废弃,需绕过)。
常见错误是直接在Location块里写Substitute却没启用过滤链,结果完全不生效;或者用SetOutputFilter但没声明CONTENT_TYPE匹配,导致JSON被跳过。
- 确保
mod_substitute和mod_filter已加载:a2enmod substitute filter - 脱敏只对
text/*和application/json等明确声明的MIME类型生效,需显式用AddOutputFilterByType SUBSTITUTE text/html application/json - 若后端返回
Content-Type: application/json; charset=utf-8,Apache默认不识别带参数的类型,得用AddOutputFilterByType SUBSTITUTE application/json(忽略charset部分)
脱敏规则要写成s///g格式,且必须转义JSON中的双引号和反斜杠
比如想把身份证号"idCard":"11010119900307251X"替换成"idCard":"*************X",不能直接写s/"idCard":"[0-9]{17}[0-9X]"/"idCard":"*************X"/g——因为双引号在Substitute指令里是分隔符,必须用\转义;而正则里的\d在Apache里不支持,得用[0-9]。
更麻烦的是,如果原始JSON含换行或空格(如pretty-print格式),mod_substitute默认按行处理,跨行匹配会失败。解决方案是加i标志并确保响应未被压缩(SetEnv no-gzip 1)。
- 正确写法:
Substitute "s/\"idCard\":\"[0-9]{17}[0-9X]\"/\"idCard\":\"*************X\"/g" - 敏感字段含斜杠(如手机号
"phone":"/138****1234/")时,改用#作分隔符:Substitute "s#\"phone\":\"[0-9]{11}\"#\"phone\":\"****1234\"#g" - 避免匹配到非目标字段:加单词边界
\b(但Apache不支持,改用前后固定字符串锚定,如\"idCard\":\"[0-9]{17}[0-9X]\")
LocationMatch比Location更适合业务路径匹配,但正则要小心贪婪匹配
用Location /api/v1/user会同时匹配/api/v1/user/profile和/api/v1/user/list,但如果你只想脱敏/api/v1/user/detail返回的身份证字段,就得用LocationMatch写精确正则。常见坑是写^/api/v1/user/.*$——.*太宽泛,可能误中其他接口;或者忘了转义路径里的/,导致语法错误。
- 匹配单个路径:
<locationmatch></locationmatch>(结尾$防扩展) - 匹配带查询参数的路径:
<locationmatch></locationmatch>——注意?要转义为\?,否则被当正则元字符 - 若路径含版本号变量(如
/api/v2/),用LocationMatch "^/api/v[12]/user/detail$",别用v\d+(Apache正则不支持+量词)
生产环境必须关掉mod_substitute的调试日志,否则性能暴跌
Substitute是逐行扫描响应体做正则替换,开启LogLevel substitute:debug会让每行输出匹配详情,I/O暴涨,QPS可能跌掉70%。更隐蔽的问题是:它不支持流式处理,整个响应体必须缓存到内存再替换——大文件(>1MB)易触发mod_substitute的默认缓冲上限(SubstituteMaxLineLength 1024),导致截断或报错Substitute: line too long。
- 调大缓冲:
SubstituteMaxLineLength 8192(单位字节),但别超过物理内存承受力 - 禁用调试:
LogLevel info或至少substitute:warn,线上绝不用debug - 验证是否生效:用
curl -v http://host/api/v1/user/detail 2>&1 | grep "idCard"看返回值,别只信浏览器F12(可能被前端二次渲染掩盖)
最麻烦的其实是编码——mod_substitute只认ASCII,UTF-8中文字段如果被错误解码成Latin-1,正则就全失效。务必确认后端返回的Content-Type头明确带charset=utf-8,且Apache没在中间加AddDefaultCharset覆盖它。










