17370845950

Golang如何防止Web应用中的CSRF攻击
Go标准库net/http不提供CSRF防护,因其属应用层安全策略;gorilla/csrf是成熟方案,基于双重提交Cookie模式,需32字节密钥、正确挂载中间件并前端传递X-CSRF-Token。

为什么标准库的 http.Request 不自带 CSRF 防护

Go 标准库的 net/http 完全不处理 CSRF,它只负责底层 HTTP 通信。CSRF 是应用层安全策略,需要开发者显式集成。很多新手误以为启用 http.CookieHttpOnlySecure 就能防 CSRF——其实这些只防 XSS 窃 cookie,对 CSRF 无效,因为 CSRF 攻击正是利用浏览器自动携带合法 cookie 发起请求。

gorilla/csrf 实现服务端 Token 管理

最成熟、轻量且被广泛验证的方案是 gorilla/csrf。它基于双重提交 Cookie 模式:服务端生成随机 token 存入 session(或加密签名后存 cookie),同时要求前端在表单中提交该 token(如 hidden 字段),并在每次 POST/PUT/DELETE 请求时校验一致性。

  • 必须在所有写操作路由前挂载中间件:csrf.Protect([]byte("32-byte-key")),密钥长度必须为 32 字节,否则 panic
  • HTML 表单中需插入 {{.CSRFField}}(模板渲染)或手动读取 X-CSRF-Token 响应头 + csrf.Token(r) 函数生成值
  • API 接口若用 JSON 提交,需在请求头带 X-CSRF-Token,且中间件默认只校验非 GET/HEAD/OPTIONS 方法
package main

import (
    "html/template"
    "net/http"
    "github.com/gorilla/csrf"
    "github.com/gorilla/mux"
)

var t = template.Must(template.New("base").Parse(`
{{.CSRFField}}
`)) func handler(w http.ResponseWriter, r *http.Request) { t.Execute(w, map[string]interface{}{"CSRFField": csrf.TemplateField(r)}) } func main() { r := mux.NewRouter() r.HandleFunc("/", handler).Methods("GET") r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("OK")) }).Methods("POST") // 必须包裹整个路由树 http.ListenAndServe(":8080", csrf.Protect( []byte("12345678901234567890123456789012"), // 32-byte key csrf.Secure(false), // 开发环境可关 HTTPS,生产务必设 true csrf.HttpOnly(true), )(r)) }

前端如何正确传递 Token(尤其 AJAX 场景)

CSRF 防护失效常因前端没传对 token。关键点:表单提交靠 hidden 字段;AJAX 请求必须从响应头或 DOM 中读取 token 并设进请求头,不能硬编码或复用旧值。

  • 首次 GET 页面时,服务端会在响应头写入 X-CSRF-Token,值与 csrf.Token(r) 返回一致
  • 后续 AJAX 请求需在 headers 中设置 X-CSRF-Token,且每次请求都应重新获取(token 可能轮换)
  • 避免把 token 存在 localStorage —— XSS 可读取,违背“不可预测+仅限 HTTP”原则;应依赖 httpOnly cookie 存服务端状态,前端只管传递

哪些情况会让 gorilla/csrf 失效或被绕过

不是加了中间件就万事大吉。常见失效点集中在配置和使用边界上:

  • csrf.Protect 未包裹全部写操作路由(比如漏掉某个 http.HandleFunc 直接注册的 handler)
  • 开发时设 csrf.Secure(false),但部署到 HTTPS 站点后忘记改回 true,导致 cookie 不被发送
  • 前端用 fetch 且未设 credentials: 'include',浏览器不带 cookie,服务端拿不到关联的 token 状态
  • 自定义 session 存储(如 Redis)未正确实现 Store 接口的 SaveGet,导致 token 无法持久化或校验失败

CSRF 的核心不在加密强度,而在 token 生命周期管理是否严格绑定用户会话、是否拒绝重复使用、是否隔离不同子域。哪怕用了 gorilla/csrf,如果 session 设计松散(比如 token 不绑定 IP 或 User-Agent),攻击面依然存在。