json.marshal不能直接加密敏感字段,因其仅为纯序列化过程,无字段级钩子;必须提前在数据结构层面处理,如通过自定义marshaljson方法实现字段级加密,或在handler中显式构造加密响应体。

为什么 json.Marshal 不能直接加密敏感字段
因为 json.Marshal 是纯序列化过程,不提供字段级钩子或拦截机制。你无法在它内部修改某个字段值(比如把 id_card 替换成加密后字符串)——除非提前处理好数据结构本身。
常见错误是试图在 struct tag 里写 json:"id_card,encrypt",但 Go 标准库和 Gin 都不识别这种自定义 tag,最终字段照常明文输出。
- 敏感字段必须在序列化前完成加密,不是“序列化后再加密整个 JSON 字符串”(后者破坏结构,前端难解析)
- 加密逻辑应与业务数据组装阶段耦合,而非绑定在 HTTP 返回路径上
- 别用
map[string]interface{}动态拼接再加密——类型丢失、易出错、无法做字段级加解密控制
用自定义 MarshalJSON 方法实现字段级加密
给敏感字段所在 struct 实现 MarshalJSON() 方法,是最可控、最符合 Go 惯例的方式。Gin 的 c.JSON() 会自动调用该方法。
示例:用户信息中加密 IDCard 和 Phone
type User struct {
Name string `json:"name"`
IDCard string `json:"id_card"`
Phone string `json:"phone"`
}
func (u User) MarshalJSON() ([]byte, error) {
type Alias User // 防止递归调用
raw, err := json.Marshal(&struct {
Alias
IDCard string `json:"id_card"`
Phone string `json:"phone"`
}{
Alias: (Alias)(u),
IDCard: aesEncrypt(u.IDCard), // 你自己的加密函数
Phone: aesEncrypt(u.Phone),
})
return raw, err
}
- 必须用
type Alias User避免无限递归:否则json.Marshal(&struct{...})又触发MarshalJSON,死循环 - 加密函数(如
aesEncrypt)建议统一管理密钥、IV、编码格式(推荐 base64),避免各处硬编码 - 如果某些接口需要明文(如后台管理),不要复用同一 struct,应拆分 DTO,比如
UserPublic/UserAdmin
Gin 中全局拦截 c.JSON() 的风险与替代方案
有人想通过中间件重写 c.JSON() 行为,统一加密所有响应中的敏感字段。这不可行,也不推荐。
原因很实际:
- Gin 的
c.JSON()底层就是json.Marshal+WriteHeader/Write,中间件无法劫持序列化过程 - 若用反射遍历所有返回 struct 字段并匹配字段名(如含
"card"或"phone"),极易误伤(比如CarBrand被加密)、漏判(大小写/别名)、性能差 - JSON 响应可能是
string、[]byte、甚至第三方库的自定义类型,无法一概而论
真正可行的替代:在 handler 里显式构造加密后的响应体
func getUser(c *gin.Context) {
u := db.GetUserByID(123)
resp := map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"name": u.Name,
"id_card": aesEncrypt(u.IDCard),
"phone": aesEncrypt(u.Phone),
},
}
c.JSON(200, resp)
}
虽然略显重复,但清晰、可测、无隐式行为。
加密后字段长度突变导致前端解析失败
这是上线后最容易被忽略的问题:AES 加密后字符串长度固定(比如 128bit → base64 后 24 字符),而原始手机号是 11 位,身份证是 18 位。前端若用 typeof value === 'string' && value.length === 11 做校验,会直接失败。
- 加密后建议统一加前缀标识,例如
"enc_abc123...",便于前端快速识别是否已加密 - 前后端需约定加密字段的 JSON 类型不变(仍是 string),但内容格式变为密文,避免改 schema
- 测试时务必覆盖「加密字段为空字符串」「加密字段为 null」两种边界,AES 加密空串可能生成非空密文,导致语义失真
加密不是加个壳就完事——它改变了数据契约。字段级加密的本质,是让敏感字段从“原始值”变成“受控密文”,这个转变必须被整个链路(DB → Go struct → JSON → 前端)共同认知和尊重。











