Gin默认不内置CSRF防护因其极简设计哲学,需手动结合session与中间件实现Token生成、透出及校验;SameSite=Lax仅作辅助,Token校验才是兜底关键。

直接上手 Gin 防 CSRF,不需要引入重型中间件也能见效——关键不是“有没有防”,而是“防在哪、怎么验、漏在哪”。
为什么 Gin 默认不带 CSRF 防护?
Gin 是一个极简框架,设计哲学是“不替你做决定”。它不自动注入 Token、不绑定 Session 存储、也不拦截 POST 请求校验字段。这意味着:gin.Default() 启动的服务,只要用了 session 且没手动加验证,就默认暴露在 CSRF 攻击下。
常见错误现象:后台管理页的删除按钮点一下就删了,但用户根本没主动提交表单;转账接口被藏在恶意页面里自动触发——这些都不是“功能 bug”,而是缺失 Token 校验的典型表现。
使用场景集中在:POST/PUT/DELETE 等非幂等操作,尤其是涉及用户状态变更(如改密码、删账号、发消息)的路由。
- 不要依赖前端 JS 生成 Token 并存 localStorage —— 攻击者可伪造整个请求体
- 不要把 Token 写死在 HTML 模板里且长期不刷新 —— 会降低重放攻击门槛
- 别用
GET /delete?id=123做敏感操作 —— 浏览器预加载、Referer 泄露、日志记录都会放大风险
用 sessions + 中间件手动注入和校验 Token
这是最可控、兼容性最好、也最容易排查问题的方式。核心逻辑是:每次请求生成/复用一个 Token,存在 session.Values["csrf"] 里,并在响应中透出给前端;后续敏感请求必须携带该值,服务端比对。
实操建议:
- 初始化 session 时确保配置了
Secure和SameSite:比如cookie.SameSite = http.SameSiteLaxMode,避免跨站携带 - 在中间件中统一读写 Token:
session.Get("csrf")不存在就生成并存入,再通过c.SetCookie()或模板变量透出 - 校验只对特定方法+路径生效,例如:
c.Request.Method == "POST" && strings.HasPrefix(c.Request.URL.Path, "/api/") - 前端必须从响应中取 Token(比如
meta[name="csrf-token"]或 JSON 字段),不能靠 JS 随机生成
示例片段(简化版):
func csrfMiddleware(store sessions.Store) gin.HandlerFunc {
return func(c *gin.Context) {
session, _ := store.Get(c.Request, "session-name")
token, ok := session.Values["csrf"].(string)
if !ok || token == "" {
token = uuid.New().String()
session.Values["csrf"] = token
session.Save()
}
c.Set("csrf_token", token)
// 写入响应头供 AJAX 使用
c.Header("X-CSRF-Token", token)
if c.Request.Method == "POST" {
submitted := c.PostForm("csrf_token")
if submitted != token {
c.AbortWithStatusJSON(403, gin.H{"error": "invalid csrf token"})
return
}
}
c.Next()
}
}
SameSite Cookie 是基础,但不能替代 Token
SameSite=Lax 能挡住大部分通过 <a></a>、<form method="GET"></form> 发起的 CSRF,但它对 POST 表单(尤其带 enctype="application/x-www-form-urlencoded")或 fetch/XHR 请求无效——现代浏览器仍允许跨站 POST 提交并携带 Cookie。
所以:SameSite 必须设,但只是“减分项”,不是“及格线”。真正兜底的是 Token 校验。
- 设置
http.Cookie.SameSite = http.SameSiteLaxMode是最低要求;高敏操作可升为Strict -
Secure=true在生产环境必须开启,否则SameSite可能被忽略 - 不要设
SameSite=None除非真有跨站嵌套需求,且必须配Secure=true
容易被忽略的三个细节
Token 生命周期没管好,等于白加。
- Session 过期后,Token 不自动失效 —— 用户登出或超时后,旧 Token 仍可能被重放,需在
logout逻辑里清空session.Values["csrf"] - 前后端 Token 传输方式不一致 —— 比如后端往
X-CSRF-Token写,前端却从form input[name=csrf_token]读,导致永远对不上 - 静态资源路径(如
/assets/js/app.js)也被中间件拦截校验 —— 应该排除strings.HasPrefix(c.Request.URL.Path, "/assets/")类路径
复杂点从来不在“怎么加”,而在于“什么时候删、谁来刷新、错在哪一层”。Token 错位一次,防御就形同虚设。











