Go标准库net/http不提供CSRF防护,因其属应用层安全策略;gorilla/csrf是成熟方案,基于双重提交Cookie模式,需32字节密钥、正确挂载中间件并前端传递X-CSRF-Token。
http.Request 不自带 CSRF 防护Go 标准库的 net/http 完全不处理 CSRF,它只负责底层 HTTP 通信。CSRF 是应用层安全策略,需要开发者显式集成。很多新手误以为启用 http.Cookie 的 HttpOnly 或 Secure 就能防 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{{.CSRFField}}(模板渲染)或手动读取 X-CSRF-Token 响应头 + csrf.Token(r) 函数生成值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(`
`))
func handler(w http.ResponseWriter, r *http.Request) {
t.Execute(w, map[string]interface{}{"CSRFField": csrf.TemplateFiel
d(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))
}
CSRF 防护失效常因前端没传对 token。关键点:表单提交靠 hidden 字段;AJAX 请求必须从响应头或 DOM 中读取 token 并设进请求头,不能硬编码或复用旧值。
X-CSRF-Token,值与 csrf.Token(r) 返回一致headers 中设置 X-CSRF-Token,且每次请求都应重新获取(token 可能轮换)httpOnly cookie 存服务端状态,前端只管传递gorilla/csrf 失效或被绕过不是加了中间件就万事大吉。常见失效点集中在配置和使用边界上:
csrf.Protect 未包裹全部写操作路由(比如漏掉某个 http.HandleFunc 直接注册的 handler)csrf.Secure(false),但部署到 HTTPS 站点后忘记改回 true,导致 cookie 不被发送fetch 且未设 credentials: 'include',浏览器不带 cookie,服务端拿不到关联的 token 状态Store 接口的 Save 和 Get,导致 token 无法持久化或校验失败CSRF 的核心不在加密强度,而在 token 生命周期管理是否严格绑定用户会话、是否拒绝重复使用、是否隔离不同子域。哪怕用了 gorilla/csrf,如果 session 设计松散(比如 token 不绑定 IP 或 User-Agent),攻击面依然存在。