请求体脱敏须在c.Next()前完成,重设r.Body并同步更新Content-Length等字段;响应体脱敏需包装c.Writer实现缓冲;日志脱敏须通过接口显式控制,禁用字符串替换和正则扫描JSON。

请求体脱敏必须在 c.Next() 前完成,且要重设 r.Body
直接 io.ReadAll(r.Body) 会导致后续 c.ShouldBindJSON() 拿不到数据——body 是单次读取流。关键不是“能不能读”,而是“读完怎么还回去”。
- 开头用
http.MaxBytesReader包一层:r.Body = http.MaxBytesReader(c.Writer, r.Body, 1024*1024),防 OOM(上限按业务定) - 跳过 GET 请求:
if c.Request.Method == "GET" { goto skipBody },避免读空 body 报错 - 对
application/json:用json.Unmarshal解析成map[string]interface{},递归遍历 key;命中预设敏感键(如"password"、"token"、"idCard")且 value 类型为string时替换为"***" - 对
application/x-www-form-urlencoded或multipart/form-data:优先调c.Request.ParseForm(),再遍历c.Request.PostForm - 脱敏后必须同步更新:
r.Body = bytes.NewReader(buf)、r.ContentLength = int64(buf.Len())、r.Header.Set("Content-Length", strconv.Itoa(buf.Len())) - 别忘了 query 和 header:
r.URL.RawQuery要重写,Authorization、Cookie等 header 也得手动脱敏
响应体脱敏必须包装 c.Writer,不能等 c.Next() 后读
c.Writer 不是缓冲区,它一旦调用 WriteHeader() 或首次 Write(),数据就直发 TCP。中间件里根本拿不到原始字节——所以不能靠“事后补救”。
- 定义结构体实现
http.ResponseWriter接口,内嵌原c.Writer,并持有一个bytes.Buffer - 重写
Write([]byte):把数据先写进 buffer;重写WriteHeader(int):缓存状态码 -
c.Next()后检查c.Request.Header.Get("Content-Type")是否含"application/json",是则从 buffer 取出字节,json.Unmarshal →脱敏 →json.Marshal →再调原Write() - 非 JSON 类型(如
text/html、image/png)直接透传,避免破坏静态资源或文件下载 - 脱敏后响应长度可能变化,需重置
Content-Lengthheader
日志字段脱敏不能靠中间件自动识别,必须显式控制输出
中间件没有结构体语义,zap.Any("user", user) 或 slog.Any("req", req) 会原样打出所有字段——日志库不扫描字段名,也不理解 “password” 是敏感词。
- 对结构体实现
zapcore.ObjectMarshaler(指针接收器!)或slog.LogValuer接口,只在字段被日志系统访问时动态返回"[REDACTED]" - 字段名必须导出(首字母大写),否则反射不可见;不要在
MarshalLogObject里解引用未判空的指针 - 别用
fmt.Sprintf("user: %+v", user),%+v 会完整展开所有字段,脱敏已无意义 - query 参数脱敏要单独处理:
r.URL.Query()遍历后,用新值重建r.URL.RawQuery,否则日志里还是原始串 - 敏感键列表建议从配置加载,支持正则(如
^.*key$),避免硬编码
别碰字符串替换和正则扫 JSON 字节流
在中间件里对响应体做 strings.ReplaceAll 或正则匹配手机号/邮箱,是最常踩的坑之一。
- 会破坏 JSON 结构(比如把
"phone":"138****1234"里的****当成真实内容再替换一次) - 误杀字段名(
"user_password_reset_url"中的password)或正常星号 - 触发
http: multiple response.WriteHeader calls错误 - 无法处理流式响应(如
text/event-stream) - 真正可控的入口是
json.Marshaler接口,或统一响应结构体 + 字段标签(如json:"phone,redact")
Content-Length header,导致 c.ShouldBindJSON() 失败报 invalid character;还有就是响应体脱敏只处理了 JSON,却漏掉了 text/plain 类型的错误提示——里面也可能含 token 或 traceID。


















