go敏感信息脱敏必须分场景独立处理:http响应需实现json.marshaler接口,日志输出需实现marshallogobject或interfacehook,配置加载需用mapstructure.decodehookfunc,三者不可互相替代。

Go 里对敏感信息做脱敏,**不能靠中间件统一扫 body 或日志字符串**——这种做法上线后必翻车:JSON 结构被破坏、字段消失、panic、漏脱、误杀全都会发生。真正可控的出口只有三个:结构体序列化(json.Marshaler)、日志对象编码(MarshalLogObject)、配置反序列化(mapstructure.DecodeHookFunc),且必须分场景独立处理。
HTTP 响应脱敏必须实现 json.Marshaler 接口
gin 中间件拿不到结构体语义,c.Next() 后也读不到响应体(c.Writer 已 flush)。唯一可靠路径是让业务结构体自己控制 JSON 输出。
- 必须用指针接收器:
func (u *User) MarshalJSON() ([]byte, error),否则嵌套字段或 nil 指针会 panic - 防递归要定义别名类型:
type userAlias User,再用(*userAlias)(u)转换后调用json.Marshal - 脱敏值禁用空字符串:若字段带
json:",omitempty",脱敏成""就会整个 key 消失;建议用"[REDACTED]"或固定掩码如"*** **** ***" - 权限判断不能塞进方法签名(
json.Marshal不传context.Context),得靠闭包捕获,比如redactEnabled := isRedactEnabled(c.Request.Context())后在闭包内使用
日志输出脱敏要看日志库类型
直接传结构体给 zap.Any 或 zerolog.Interface,敏感字段原样打印——日志库不识别字段语义,也不扫描字段名。
- zap 用户必须实现
zapcore.ObjectMarshaler接口(即MarshalLogObject(enc zapcore.ObjectEncoder)),且字段必须导出(Password string,不是password string) - zerolog 没有结构体级钩子,只能用
InterfaceHook,但必须递归处理map[string]interface{},并跳过非 map 类型(int、time.Time等直接返回原值) - 别在
MarshalLogObject里解引用未判空的指针(如u.Profile.Token),否则日志写入时 panic - HTTP 请求参数脱敏要单独处理:
r.URL.Query()和r.Header需手动遍历,r.URL.RawQuery必须同步更新,否则日志里看到的是未脱敏原始串
配置加载阶段就要脱敏,不能等 viper.Unmarshal 完再补救
viper.Unmarshal 执行完,database.password 这类字段早已明文驻留内存。安全时机只在反序列化过程中。
- 用
mapstructure.DecodeHookFunc拦截键路径,例如匹配"password"、"secret"、"api_key"等关键词 - 不要依赖调用栈解析 key path(不可靠),建议结合
viper.AllKeys()+ 正则预筛 - 函数签名中
f.Kind() != reflect.String || t.Kind() != reflect.String要先判断,避免非字符串类型误处理 - 脱敏逻辑只替换 value,不改动类型;数字、bool、struct 不动,仅对 string 类型替换为
"***"
最常被忽略的点:同一个字段在不同出口要分别脱敏——HTTP 响应走 MarshalJSON,日志走 MarshalLogObject 或 InterfaceHook,配置加载走 DecodeHookFunc,三者互不替代。少一个,敏感数据就裸奔一次。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











