Beego 无内置表单防重机制,需手动实现:在 GET 中生成 UUID Token 并存入 Redis/session,模板中通过 {{.form_token}} 插入隐藏域;POST 开头校验并立即删除 Token,确保原子性。

Beego 本身不提供开箱即用的表单重复提交过滤机制,必须手动集成 token + 后端校验逻辑,否则任何前端禁用按钮都形同虚设。
beego 中如何生成和注入一次性 form token
Beego 没有内置 csrf_token 或 form_token 辅助函数,需自行在 Controller 中生成并透出到模板。常见做法是用 uuid.New() 生成字符串,存入 session 或 Redis,并绑定当前用户+时间戳防伪造:
- 在 GET 处理函数中调用
ctx.Input.Session("form_token") = token(session 需提前启用) - 或更安全地存入 Redis:
redis.SetEX("form:token:"+uid, token, 300)(5 分钟过期) - 模板中通过
{{.form_token}}插入隐藏域:<input type="hidden" name="token" value="{{.form_token}}"> - 注意:不能只依赖客户端传来的 token 值,必须和服务端存储的做严格比对,且比对后立即删除(
DEL或GETSET)
beego controller 中校验 token 的典型位置和写法
校验必须放在 POST 处理逻辑最开始,且要在事务或数据库写入前完成。推荐在 Post() 方法开头统一拦截:
- 从
ctx.Input.Query("token")或ctx.Input.Post("token")取值(取决于前端传法) - 查 session:
if tk, ok := ctx.Input.Session("form_token").(string); !ok || tk != token { ctx.Abort("400") } - 或查 Redis:
val := redis.Get("form:token:" + uid).Val(); if val != token { ctx.Abort("400") }; redis.Del("form:token:" + uid) - 不要用
InsertFilter做全局 token 校验——它无法区分哪些路由是表单提交,容易误杀 API 接口 - 若校验失败,必须返回明确错误(如
ctx.Data["json"] = map[string]string{"error": "duplicate submit"}),而不是跳转或静默忽略
为什么 beego 的 Form(&T{}) 不自带防重逻辑
Beego 的 beego.Form() 只负责结构体映射和基础验证(valid: tag),它不感知请求上下文、不管理状态、也不介入 HTTP 生命周期。它的设计目标是“渲染+绑定”,不是“幂等控制”:
-
beego.Form(&User{})渲染的表单不含 token 字段,除非你手动加<input name="token"> - 即使你在 struct tag 里写
token string `form:hidden`,它也不会自动赋值,仍需 Controller 显式设置.Data["form_token"] - 所有 token 生效范围、生命周期、存储介质(session/redis)、失效策略,都得你自己决定和实现
- 如果你用了
beego.InsertFilter做通用校验,记得避开静态资源路径(如/static/)和登录接口,否则会阻断正常流程
真正难的不是生成 token,而是确保“校验 → 删除 → 业务执行”三步原子性;Redis 的 GETDEL(6.2+)或 Lua 脚本是较稳妥的选择,单纯用两个命令存在竞态风险。











