脱敏中间件必须在序列化前生效,即在ctx.JSON()调用前对Go结构体做字段级处理,而非响应后字符串替换;需禁用默认ctx.JSON(),改用封装的ctx.WriteJSON()配合自定义marshaler和mask结构体tag实现精准脱敏。

脱敏中间件必须在序列化前生效
HTTP 响应体里的敏感字段(如手机号、身份证号)如果已经 JSON 序列化完成,再想“过滤”就只能做字符串替换——这极易误伤、不可靠、无法处理嵌套结构。真正可行的时机是:在 iris.Context.JSON() 或 iris.Context.XML() 调用前,对即将输出的 Go 结构体做字段级处理。所以脱敏中间件本质是“响应前结构体转换器”,不是“响应后文本扫描器”。
用自定义 JSON 编码器 + 中间件拦截输出
Iris 没有内置字段级脱敏钩子,得自己接管序列化流程。核心思路是:禁用默认 ctx.JSON(),改用封装后的 ctx.WriteJSON(),并在其中注入脱敏逻辑。中间件只负责标记上下文是否启用脱敏,不直接操作数据。
- 在中间件里用
ctx.Values().Set("sensitive_mask", true)标记本次请求需脱敏 - 定义一个全局
json.Marshal替代函数,接收任意interface{},递归检查字段是否带mask:"mobile"这类 tag,并根据上下文决定是否替换 - 所有 handler 改用
ctx.WriteJSON(data)(而非原生ctx.JSON()),该函数内部读取ctx.Values().Get("sensitive_mask")并调用定制 marshaler
字段 tag 设计与常见脱敏规则映射
别依赖运行时反射判断字段名(比如硬写 "phone"),容易漏匹配、难维护。统一用 struct tag 控制,例如:
type User struct {
Name string `json:"name" mask:"chinese_name"`
Phone string `json:"phone" mask:"mobile"`
IDCard string `json:"id_card" mask:"id_card,1,2"`
}
其中 mask:"id_card,1,2" 表示身份证脱敏保留前 1 位、后 2 位;mask:"mobile" 表示手机号中间 4 位打 *。脱敏函数根据 tag 值分发到对应逻辑,比 if-else 判字段名更清晰、可扩展。
注意 context 生命周期和并发安全
脱敏中间件本身不耗资源,但定制 marshaler 若引入全局 map 缓存反射结果,必须加 sync.RWMutex;更关键的是:所有脱敏操作必须基于请求上下文,不能缓存未脱敏原始值到包级变量——否则高并发下会串数据。另外,ctx.Values() 是 request-scoped 的,放心存标记,但别存大对象。
最易被忽略的一点:如果你在中间件里调用了 ctx.ReadJSON() 提前解析请求体,又没做入参脱敏,那攻击者可能直接 POST 已脱敏的假数据绕过校验——脱敏是双向的,输入和输出都得看住。


















