GORM 数据脱敏处理
GORM 通过 AfterFind 钩子在数据查询后自动脱敏敏感字段,确保返回业务层的隐私数据已做掩码处理。
脱敏机制概述
脱敏与加密的区别
| 特性 | 脱敏 | 加密 |
|---|---|---|
| 目的 | 展示时隐藏部分信息 | 存储时保护数据 |
| 可逆性 | 不可逆 | 可逆 |
| 场景 | 日志、列表展示、接口返回 | 数据库落盘存储 |
常见脱敏规则
手机号脱敏
Go
func MaskPhone(phone string) string {
if len(phone) < 7 {
return "***"
}
return phone[:3] + "****" + phone[len(phone)-4:]
}
// 示例: 13812345678 -> 138****5678
身份证脱敏
Go
func MaskIDCard(idCard string) string {
if len(idCard) < 10 {
return "***"
}
return idCard[:6] + "********" + idCard[len(idCard)-4:]
}
// 示例: 110101199001011234 -> 110101********1234
邮箱脱敏
Go
func MaskEmail(email string) string {
parts := strings.Split(email, "@")
if len(parts) != 2 {
return "***"
}
local := parts[0]
if len(local) <= 2 {
return "**@" + parts[1]
}
return local[:2] + "***@" + parts[1]
}
// 示例: zhang@example.com -> zh***@example.com
姓名脱敏
Go
func MaskName(name string) string {
runes := []rune(name)
if len(runes) == 0 {
return "*"
}
if len(runes) == 1 {
return "*"
}
return string(runes[0]) + "*"
}
// 示例: 张三 -> 张*
// 示例: 欧阳修 -> 欧**
模型钩子实现
AfterFind 自动脱敏
Go
type Customer struct {
ID uint `gorm:"primaryKey"`
Name string
Phone string
IDCard string
Email string
// 脱敏字段(不映射数据库)
MaskedPhone string `gorm:"-:all"`
MaskedIDCard string `gorm:"-:all"`
MaskedEmail string `gorm:"-:all"`
}
func (c *Customer) AfterFind(tx *gorm.DB) error {
c.MaskedPhone = MaskPhone(c.Phone)
c.MaskedIDCard = MaskIDCard(c.IDCard)
c.MaskedEmail = MaskEmail(c.Email)
return nil
}
// 查询时自动脱敏
var customer Customer
db.First(&customer, 1)
// customer.MaskedPhone = "138****5678"
// customer.MaskedIDCard = "110101********1234"
选择性脱敏
上下文控制脱敏开关
Go
type ContextKey string
const SkipMaskingKey ContextKey = "skip_masking"
func (c *Customer) AfterFind(tx *gorm.DB) error {
// 检查上下文是否跳过脱敏
if skip, ok := tx.Statement.Context.Value(SkipMaskingKey).(bool); ok && skip {
return nil
}
c.MaskedPhone = MaskPhone(c.Phone)
c.MaskedIDCard = MaskIDCard(c.IDCard)
c.MaskedEmail = MaskEmail(c.Email)
return nil
}
// 管理员查询跳过脱敏
ctx := context.WithValue(context.Background(), SkipMaskingKey, true)
db.WithContext(ctx).First(&customer, 1)
注意: 跳过脱敏需严格权限控制,仅限管理员或审计场景使用。
批量查询脱敏
列表场景自动脱敏
Go
func ListCustomers(db *gorm.DB) ([]Customer, error) {
var customers []Customer
err := db.Find(&customers).Error
// AfterFind 钩子会自动对每条记录脱敏
return customers, err
}
脱敏与序列化
JSON 输出控制
Go
type CustomerResponse struct {
ID uint `json:"id"`
Name string `json:"name"`
Phone string `json:"phone"` // 输出脱敏后的值
}
func ToCustomerResponse(c *Customer) CustomerResponse {
return CustomerResponse{
ID: c.ID,
Name: c.Name,
Phone: c.MaskedPhone, // 使用脱敏字段
}
}
// API 返回脱敏数据
customer, _ := ListCustomers(db)
resp := ToCustomerResponse(&customer[0])
json.NewEncoder(w).Encode(resp)
注意: 不要在模型字段上直接打
jsontag,应通过转换函数控制输出,避免误泄露原始数据。
要点总结
- 使用
AfterFind钩子在查询后自动脱敏敏感字段 - 手机号保留前3后4,身份证保留前6后4,邮箱保留前缀前2位
- 脱敏不可逆,仅用于展示,存储保护需使用加密
- 可通过上下文控制跳过脱敏,适合管理员或调试场景
- API 输出应通过转换函数使用脱敏字段,避免直接序列化原始数据
存放路径:D:\git2\jwdev\articles\GORM\专家\安全与数据保护\数据脱敏处理.md
📝 发现内容有误?点击此处直接编辑