gin 本身不内置 csrf 防护,必须手动集成中间件或自行实现;gin-csrf 是目前最轻量、兼容 gin-contrib/sessions 的现成方案,但默认不校验 samesite 和 https 环境。samesite=lax 虽可拦截多数跨站 post 请求,但老版本浏览器不支持,且对部分 get 请求仍放行,无法替代 csrf token 校验。

直接上结论:Gin 本身不内置 CSRF 防护,必须手动集成中间件或自行实现;gin-csrf 是目前最轻量、兼容 gin-contrib/sessions 的现成方案,但默认不校验 SameSite 和 HTTPS 环境,这点容易被忽略。
为什么不能只靠 SameSite Cookie 就算防住了 CSRF?
SameSite 属性(如 SameSite=Lax)确实能拦截大部分跨站 POST 请求,但它不是银弹:
- 老版本浏览器(如 Chrome SameSite,降级后形同虚设
-
SameSite=Lax允许 GET 请求携带 Cookie,而攻击者可用<img src="POST-endpoint">或诱导点击链接触发敏感 GET 接口(如删除资源) - 如果业务强制要求支持 IE 或旧安卓 WebView,
SameSite基本无效 -
SameSite=Strict对用户体验干扰大(例如从邮箱点击链接进站会丢失会话)
所以生产环境必须搭配服务端 Token 校验,不能只依赖 Cookie 属性。
用 gin-csrf 中间件时,session 存储必须显式配置
gin-csrf 默认使用内存 session,重启即失效,且无法在多实例部署下共享 Token —— 这会导致用户刷新页面后反复 403。
正确做法是绑定你已有的 session 存储后端:
- 若用
gin-contrib/sessions+ Redis,需确保csrf.New传入的session.Store与主 session 使用同一实例 - 不要复用
session.NewCookieStore,它和你的登录 session 不互通,CSRF Token 会被当成独立会话处理 - 初始化示例:
store := sessions.NewRedisStore(10, "tcp", "localhost:6379", "", []byte("secret"))<br>csrfMiddleware := csrf.New(csrf.Options{<br> SessionStore: store,<br> Secret: "your-32-byte-secret-here",<br>}) -
Secret必须是 32 字节(不是字符串长度),否则启动报错:crypto/aes: invalid key size
表单提交时 Token 必须放在 hidden input,不能只靠 header
CSRF 攻击常通过 <form method="POST"></form> 发起,浏览器不会自动带自定义 header(如 X-CSRF-Token),所以仅校验 header 会误杀合法表单请求。
正确做法是双通道传递:
- 服务端渲染模板时,把 Token 写进
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}"> - 同时设置 Cookie:
Set-Cookie: csrf_token=xxx; Path=/; HttpOnly=false; SameSite=Lax(HttpOnly=false才能让 JS 读取) - 前端 AJAX 请求可额外从 Cookie 取值,塞进
X-CSRF-Tokenheader,但表单 submit 依赖 hidden input - 中间件默认检查
csrf_token参数名,如改名需同步设置FieldName选项
本地开发 HTTP 环境下,Secure=true 会导致 Cookie 不发送
很多教程直接复制示例代码,把 Secure: true 写死,结果本地 http://localhost:8080 下 Cookie 根本不下发,后续所有 POST 都 403。
安全但实用的做法:
- 根据环境动态开关:
Secure: os.Getenv("ENV") == "production" - 开发时用
Secure: false,但务必加SameSite: http.SameSiteLaxMode补位 - 测试阶段打开浏览器 DevTools → Application → Cookies,确认
csrf_token是否存在且 Path 匹配(比如设成/api就收不到根路径的请求) - 别信“本地 HTTPS 调试”——除非真配了证书,否则
Secure=true就是摆设
真正容易翻车的地方不在逻辑,而在 Cookie 的 Path、Domain、Secure 组合是否匹配请求上下文;一次 403,先查 Cookie 有没有、有没有被浏览器丢弃、有没有被路径过滤掉。











