Gin 默认日志会暴露密码字段,因其默认中间件原样打印请求体中的敏感字段(如password);需自定义中间件在ShouldBind前脱敏Body、Header和Query,用JSON递归遍历或带锚点正则处理,避免误替换。

为什么 Gin 默认日志会暴露密码字段
Gin 的 gin.Default() 和 gin.Logger() 中间件默认使用 log.Printf 打印请求信息,包括 POST 表单、JSON 请求体中的原始数据。一旦用户提交含 password、pwd、secret 等字段的请求,这些值就会原样出现在日志里。
用自定义 LoggerMiddleware 替换默认日志中间件
不能直接修改 gin.Logger(),必须手写中间件,在读取并解析请求体前做敏感字段脱敏。关键点是:必须在 c.ShouldBind() 或 c.GetRawData() 之前完成处理,否则 Body 已被读取、不可重复读。
- 用
c.Request.Body读一次原始 Body,用正则或 JSON 解析器抹掉敏感键(如password),再构造新io.ReadCloser赋给c.Request.Body - 只对
Content-Type: application/json和application/x-www-form-urlencoded做处理;其他类型(如文件上传)跳过,避免破坏二进制流 - 脱敏后日志中仍保留字段名和结构,仅将值替换为
"***",便于排查格式问题而不泄露真实值 - 示例片段:
body, _ := io.ReadAll(c.Request.Body) c.Request.Body = io.NopCloser(strings.NewReader(string(sanitizeJSON(body))))
sanitizeJSON 函数要小心处理嵌套与边界
别用简单字符串替换,比如 strings.ReplaceAll(string(body), `"password":"xxx"`, `"password":"***"`) —— 容易误杀注释、字段名子串(如 "old_password")、或 JSON 格式错位导致后续解析失败。
- 优先用
json.Unmarshal+ 递归遍历 map[string]interface{},对匹配键名(忽略大小写)的字符串值设为"***",再json.Marshal回写 - 若担心性能或非标准 JSON(带注释、尾逗号),改用正则但加锚点:
regexp.MustCompile(`("password"s*:s*)"(?:[^"\]|\.)*"`),只替换完整字符串字面量 - 注意空值、
null、数字型密码(极少见但存在)——统一按字符串键匹配,不判断值类型
日志输出时仍需过滤 Header 和 Query 中的敏感参数
Body 脱敏只是半程。用户可能把密码放 Authorization 头(如 Basic xxx)、token 查询参数、甚至 X-Api-Key 里。这些不会进 Body 处理流程,得在日志打印前单独清洗。
- 在中间件末尾打印日志前,调用一个
cleanLogFields(c *gin.Context),检查c.Request.URL.Query()和c.Request.Header - 对已知敏感头(
Authorization、Cookie、X-Api-Key)统一设为"***";Query 中password、key等参数值同样替换 - 不要试图“智能识别”所有密钥——固定白名单更可控;过度扫描反而容易漏或误判
Gin 日志脱敏真正难的不是代码量,而是 Body 重写后保持 HTTP 流完整性,以及 Header/Query/Body 三处清洗逻辑必须全部覆盖,缺一不可。稍有遗漏,审计时就可能看到明文密码。











