必须手动将 csrf.Token(c) 传入模板并渲染为隐藏字段:在处理函数中调用 c.HTML() 前设 c.Set("csrf_token", csrf.Token(c)),模板中写 <input type="hidden" name="csrf_token" value="{{ .csrf_token }}">,确保字段名与后端校验逻辑一致。

模板里怎么插入 CSRF Token 字段
Gin 本身不内置模板 Token 注入机制,必须手动把 csrf.Token(c) 传入 HTML 模板,并在表单中渲染为隐藏字段。Gin 的 HTML 渲染函数只负责数据绑定,不会自动加 Token —— 这是开发者职责。
常见错误是直接在模板里写 {{ .CSRF }} 却没在 c.HTML() 调用时传值,结果页面渲染出空字符串,后端验证必然失败。
- 必须在每个需要防护的请求处理函数中显式调用
c.Set("csrf_token", csrf.Token(c))或类似方式传值 - 模板中统一用
<input type="hidden" name="csrf_token" value="{{ .csrf_token }}">(注意字段名要和后端校验逻辑一致) - 如果用的是自定义中间件生成 Token,确保它在所有目标路由前执行,且
c.Next()后 Token 仍可被模板访问(避免中间件提前终止上下文)
为什么 csrf.Token(c) 在模板里总为空
根本原因是 Gin 的上下文生命周期短,csrf.Token(c) 依赖当前请求的会话状态(比如 Session ID)和 Token 存储后端(如内存、Redis)。如果 Session 未初始化、Cookie 未正确设置或 Token 存储器未配置,csrf.Token(c) 就返回空字符串。
典型场景:本地开发时禁用了 Cookie(如某些浏览器隐私模式),或反向代理(Nginx)未透传 Cookie 头,导致 Gin 无法读取会话凭证,自然拿不到 Token。
- 检查是否已启用 Session 中间件(如
gin-contrib/sessions),且调用顺序在 CSRF 中间件之前 - 确认响应头中存在
Set-Cookie,且前端请求携带了该 Cookie(可用浏览器 DevTools → Application → Cookies 验证) - 若使用 Redis 存储 Session,确保连接未超时、key 前缀未冲突、序列化方式与存储一致
CSRF Token 字段名和校验逻辑必须严格匹配
Token 字段名不是固定的,csrf.Token(c) 只负责生成值,真正起作用的是你如何读取并验证它。Gin 生态常用两种方式:手动解析表单 + 自定义中间件,或用 gin-contrib/csrf。
用 gin-contrib/csrf 时,默认字段名是 _csrf,但你可以通过 csrf.SameSite(http.SameSiteLaxMode) 等选项调整行为;而自己手写校验逻辑时,字段名完全由你定,比如 csrf_token 或 x-csrf-token,但前后端必须完全一致。
- POST 表单提交走
c.PostForm("_csrf"),AJAX 请求则需从 meta 标签或 JS 变量里取值,再设到请求头(如X-CSRF-Token) - 若前端用 fetch,记得加
credentials: 'include',否则 Cookie 不发送,Token 校验直接失败 - 不要在 GET 请求里校验 Token(标准做法是仅对 state-changing 方法如 POST/PUT/DELETE 校验)
SameSite Cookie 设置不当会导致 Token 无效
即使 Token 正确生成并渲染进模板,如果 Cookie 的 SameSite 属性设为 Strict,而用户是从第三方链接跳转过来的(比如邮件里的链接),浏览器就不会发送 Cookie,后端就拿不到会话,csrf.Token(c) 就会失效。
这不是 Gin 的 Bug,而是浏览器策略。Lax 是更平衡的选择:允许安全的 GET 跳转携带 Cookie,但阻止跨站 POST 提交时附带 —— 这正好契合 CSRF 防护需求。
- Session 初始化时务必设置
sessions.CookieSameSite(http.SameSiteLaxMode) - 不要依赖 Referer 校验作为唯一手段,它容易被伪造或被隐私插件屏蔽
- Token 有效期建议控制在 12 小时内,过长增加泄露风险,过短影响用户体验(比如页面开太久提交失败)


















