Gin本身不内置CSRF防护,必须依赖第三方中间件;推荐使用gin-contrib/csrf,因其原生适配Gin上下文、默认仅校验非幂等请求(POST/PUT等),且支持Secure/HttpOnly/SameSite等安全参数配置,但需手动注入Token至模板或响应头。

Gin 本身不内置 CSRF 防护,必须依赖第三方中间件;直接用 gin-contrib/csrf 最轻量、最贴合 Gin 生态,但要注意它默认不校验 GET 请求,且 Token 不自动注入模板——这点常被忽略导致前端拿不到 Token。
为什么不用 gorilla/csrf 而选 gin-contrib/csrf
gorilla/csrf 是通用 HTTP 中间件,需手动适配 Gin 的 *gin.Context,容易漏掉 c.Request 和 c.Writer 的透传;而 gin-contrib/csrf 原生支持 Gin 上下文,Token 可通过 c.GetString("csrf_token") 直接获取。
- 它底层仍调用 gorilla/csrf,但封装了适配逻辑
- 默认只对非 GET/HEAD/OPTIONS 请求强制校验,符合 REST 安全惯例
- 不兼容 html/template 自动注入,必须显式传入(否则表单提交必 403)
如何正确初始化 gin-contrib/csrf 中间件
关键不是加中间件,而是配置参数是否匹配部署环境:
- csrf.Secure(true) 必须开启,否则 Cookie 的 Secure 标志不生效,HTTPS 下 Token 无法发送
- csrf.HttpOnly(true) 推荐开启,防止 JS 读取 Token(虽不影响校验,但降低泄露风险)
- csrf.SameSite(http.SameSiteLaxMode) 应显式设置,避免旧浏览器 fallback 到 SameSite=None 导致失效
- csrf.MaxAge(3600) 建议设为 1 小时,避免长期有效的 Token 成为攻击面
import "github.com/gin-contrib/csrf"
<p>r := gin.Default()
r.Use(csrf.New(
csrf.Secret("your-32-byte-secret-key-here"),
csrf.Secure(true),
csrf.HttpOnly(true),
csrf.SameSite(http.SameSiteLaxMode),
csrf.MaxAge(3600),
))</p>
前端如何安全使用 CSRF Token
Token 不会自动写入 HTML 或响应头,必须手动塞进模板或 API 响应:
- 模板中用 {{.CsrfToken}}(需在 handler 里显式 c.HTML(..., gin.H{"CsrfToken": c.GetHeader("X-CSRF-Token")}))
- AJAX 请求必须从 X-CSRF-Token 响应头读取,并作为 X-CSRF-Token 请求头发出
- 表单隐藏域要写成 <input type="hidden" name="_csrf" value="{{.CsrfToken}}">,不能硬编码或拼接
- 若用 Vue/React,建议在首次 HTML 响应时把 Token 注入全局变量,而非每次 API 调用都额外请求
哪些请求会被跳过 CSRF 校验
gin-contrib/csrf 默认跳过以下方法,这是设计使然,不是 bug:
- GET、HEAD、OPTIONS:幂等操作无需防护
- 路径匹配 /api/v1/health 这类公开接口,需手动用 csrf.SkipPaths("/api/v1/health") 显式排除
- 如果你有自定义的 PATCH 或 DELETE 表单,务必确认它们没被误判为 GET(比如 query 参数触发了重定向)
- Token 过期后返回的是 403 而非 401,前端需捕获该状态并刷新页面,而不是重试
CSRF 防护真正难的不是加中间件,而是确保 Token 生命周期与会话一致、前后端传输路径不被截断、以及所有敏感操作路径都被覆盖——漏掉一个表单或一个 AJAX 接口,整套机制就形同虚设。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











