必须在ctx.json()前对go结构体字段级脱敏,而非替换响应体字符串;所有提前终止响应的中间件须调用ctx.stopexecution();自定义writejson接管序列化全过程,通过struct tag控制脱敏逻辑。

中间件里改响应数据必须在 ctx.JSON() 之前
直接对响应体字符串做替换(比如用 strings.ReplaceAll(ctx.ResponseWriter().Body, "138****1234", "***"))是无效的——Iris 的 ctx.JSON() 内部调用的是 json.Marshal() 后写入 ResponseWriter,此时响应体还没生成,更不存在“字符串可替换”。真正能干预的时机,是在业务 handler 调用 ctx.JSON() 之前,对它传入的 Go 结构体做字段级处理。
常见错误现象:中间件里写了 ctx.ResponseWriter().Write([]byte("xxx")),结果响应体变成两段,或状态码仍是 200、Content-Type 缺失——因为手动 Write 绕过了 Iris 的响应封装逻辑。
- 脱敏、审计日志、统一响应包装等操作,必须基于结构体反射 + tag 控制,不能依赖响应后文本扫描
- 所有 handler 必须改用自定义的
ctx.WriteJSON(data),而不是原生ctx.JSON() - 中间件只负责设置上下文标记,例如
ctx.Values().Set("wrap_response", true),不直接修改 data - 若 handler 里用了
ctx.Text()或ctx.XML(),这类脱敏中间件默认不生效,需额外扩展 marshaler 支持
用 ctx.Values() 传递脱敏开关,别存原始数据
ctx.Values() 是 request-scoped 的键值对容器,适合存轻量标记(如布尔、字符串),但不适合存大对象或未脱敏的原始结构体。高并发下如果把用户原始 User 实例塞进 ctx.Values().Set("raw_user", u),再在后续中间件里反复读取并脱敏,极可能因 goroutine 交叉导致数据串扰。
使用场景:登录接口返回用户信息,需对手机号、身份证号字段脱敏;管理后台接口则需完整返回。
- 中间件中只设标记:
ctx.Values().Set("sensitive_mask", "admin")或ctx.Values().Set("sensitive_mask", true) - 自定义
WriteJSON函数内部读取该标记,决定是否启用字段 tag 解析(如mask:"mobile") - 避免在中间件里调用
ctx.ReadJSON(&u)提前解析请求体并缓存——这会增加内存占用,且若 handler 本身也 ReadJSON 就造成重复解析 - struct tag 如
mask:"id_card,1,2"比硬编码字段名更安全,新增字段无需改中间件逻辑
ctx.Next() 后不能再改响应,StopExecution 是关键
中间件执行顺序是 before → handler → after。如果你在 after 中间件里试图调用 ctx.JSON() 或修改响应头,此时 handler 已经写完响应,ctx.StatusCode() 和 ctx.Header() 仍有效,但 ctx.JSON() 会 panic 或静默失败(取决于底层 writer 状态)。
常见错误现象:鉴权中间件里 ctx.JSON(401, ...) 后忘了 ctx.StopExecution(),业务 handler 仍被执行,数据库被误删。
- 所有提前终止响应的中间件(如鉴权失败、参数校验不通过),必须在写响应后立刻调用
ctx.StopExecution() -
ctx.StopExecution()不等于return,它会阻止后续中间件和 handler 执行,但不会退出当前函数作用域 - 不要在
go func() { ... }()里调ctx.Next()或ctx.StopExecution()—— ctx 不跨 goroutine 安全 - 若需异步记录响应耗时或审计日志,应在 before 中存开始时间,after 中读取并打印,不干预响应流
自定义 WriteJSON 必须接管序列化全过程
Iris 没有提供 OnResponseMarshal 这类钩子,所以得自己重写序列化入口。核心是替换掉 ctx.JSON() 的行为,而不是 patch 响应体。这意味着你要自己调用 json.Marshal(),插入脱敏逻辑,再写入 ResponseWriter。
性能影响:每次响应都走反射检查 struct tag,对高频小结构体(如 {"code":0,"msg":"ok"})会有微小开销,但比 runtime 字符串正则匹配稳定得多。
- 定义全局函数
func WriteJSON(ctx iris.Context, v interface{}) error,内部先判断ctx.Values().Get("sensitive_mask") != nil - 启用脱敏时,递归遍历
v的每个字段,检查是否有masktag,并按规则替换值(如手机号中间 4 位变"****") - 无论是否脱敏,最终都走
ctx.ResponseWriter().Write(),并手动设Content-Type: application/json和状态码 - 不要复用
iris-contrib/middleware/response—— 该包已归档,v12+ 不兼容,且不支持字段级控制
ctx.ReadJSON()),又没对输入字段做同样规则的校验或清洗,攻击者可以 POST 已脱敏的假数据(如 {"phone":"138****1234"})绕过业务逻辑。输出要脱敏,输入也要盯住。











