4.6 KiB
4.6 KiB
tags, create time
| tags | create time | |||||
|---|---|---|---|---|---|---|
|
2026-04-28 00:00 |
跳过绑定 binding:"-"
概述
在 Gin 的自动绑定机制中,结构体标签 binding:"-" 用于完全排除某个字段,使其不参与任何请求数据的绑定和校验。这是防止前端篡改敏感数据的第一道防线。
正文
工作原理
Gin 在调用 ShouldBind / ShouldBindJSON 等方法时,底层会遍历结构体的所有字段,检查其 binding 标签:
type User struct {
ID uint `json:"id" gorm:"primaryKey"`
Name string `json:"name" binding:"required"` // ✅ 正常绑定 + 校验
Role string `json:"role" binding:"required"` // ✅ 正常绑定 + 校验
Admin bool `json:"admin" binding:"-"` // ❌ 完全不参与绑定
}
当请求携带 { "name": "Alice", "role": "admin", "admin": true } 时:
| 字段 | 绑定行为 | 最终值 |
|---|---|---|
Name |
从 JSON 取值 | "Alice" |
Role |
从 JSON 取值 | "admin" |
Admin |
直接忽略,读取不到 | false(Go 零值) |
为什么需要跳过绑定?
最常见且最重要的场景是权限防护。
[!CAUTION] 没有
binding:"-"的风险假设接口设计如下:
type CreateUserRequest struct { Name string `json:"name" binding:"required"` Role string `json:"role" binding:"required"` Admin bool `json:"admin"` // 忘记加 "-"! }恶意用户只需在请求中添加
"admin": true,就能把自己创建为管理员——因为 Gin 会自动把前端传来的值填入结构体。数据绑定时机远早于任何业务校验逻辑,框架不会区分"普通字段"和"特权字段"。
跳过绑定 vs 不序列化
binding:"-" 与 json:"-" 是两个不同的概念,经常组合使用:
type User struct {
ServerID uint `json:"-" gorm:"primaryKey"` // 不序列化、不绑定
Internal string `binding:"-"` // 可序列化但不绑定
Admin bool `json:"admin" binding:"-"` // 可序列化但不绑定
}
| 标签 | 影响范围 | 效果 |
|---|---|---|
binding:"-" |
只读入方向(请求 → 结构体) | 请求中的值不会被填入该字段 |
json:"-" |
只写方向(结构体 → JSON) | 该字段不会出现在 JSON 响应中 |
| 两个同时存在 | 双向隔离 | 字段完全不可见 |
flowchart TD
A["客户端请求"] --> B{"是否有 binding 排除标签"}
B -->|"是"| C["值被丢弃"]
B -->|"否"| D["填入结构体字段"]
D --> E["业务逻辑处理"]
E --> F{"是否有 json 排除标签"}
F -->|"是"| G["不出现在响应 JSON"]
F -->|"否"| H["返回给客户端"]
C -.->|"保持 Go 零值"| E
实战清单
在以下字段上务必加上 binding:"-":
- 权限相关:
Admin、IsSuperuser、PermissionLevel - 服务端生成:
ID(通常由数据库自增)、CreatedAt、UpdatedAt - 计算派生:
FullName(由FirstName + LastName拼接)、Balance(由账户记录汇总) - 内部状态:
retryCount、lockVersion、tenantID(应从 JWT Token 中提取)
提问: 如果
ID已经在路由参数中通过c.Param("id")提取了,还需要加binding:"-"吗?点击展开答案
需要。
c.Param手动提取是一回事,但如果有另一个端点用ShouldBindJSON接收更新请求,前端依然可能传入id字段来篡改目标记录(Horizontal / Vertical Privilege Escalation)。最安全的做法是在所有涉及绑定的结构体中标记binding:"-",让编译器帮你兜底。
常见误区
// ❌ 错误:只依赖手动赋值,没有显式跳过绑定
type UpdateReq struct {
Name string `json:"name" binding:"required"`
Admin bool // 没有 binding 标签 → 仍会被绑定!
}
func handler(c *gin.Context) {
var req UpdateReq
c.ShouldBindJSON(&req)
req.Admin = false // 试图覆盖,但已经晚了——恶意数据已被填入
// ...
}
// ✅ 正确:在结构体层面就拦截
type UpdateReq struct {
Name string `json:"name" binding:"required"`
Admin bool `json:"admin" binding:"-"` // 先拦截,再在后端设值
}
func handler(c *gin.Context) {
var req UpdateReq
c.ShouldBindJSON(&req)
// req.Admin 必定是 false,从外部注入不了任何值
req.Admin = isAdmin(c) // 从 Session / JWT 取真实身份
}
关联笔记
- GIN/7-binding-advanced — 父文档,高级绑定与表单处理