CSRF令牌无效:请求令牌始终为null导致403禁止访问
问题背景
我希望在请求头中传递CSRF令牌,但发送请求时令牌始终为null,触发403 Forbidden错误。以下是相关实现代码及请求信息:
1. 主函数中CSRF中间件配置
csrfMiddleware := csrf.Protect([]byte("qwnbjb13jbhb12j3bjbbj")) mainRouter.Use(csrfMiddleware) serverPort := ":" + os.Getenv("SERVER_PORT") logs.Logger.Info("starting server at ", serverPort) err = http.ListenAndServe(serverPort, mainRouter) if err != nil { logs.LogFatal(logs.Logger, "main", "main", err, "Failed to start server") } logs.Logger.Info("server stopped") }
2. 尝试在响应头中设置令牌的代码
func NewAuthHandler(authMwRouter *mux.Router, mainRouter *mux.Router, u domain.AuthUsecase) { handler := &AuthHandler{ AuthUsecase: u, } mainRouter.HandleFunc("/api/v1/auth/login", handler.Login).Methods(http.MethodPost, http.MethodOptions) mainRouter.HandleFunc("/api/v1/auth/register", handler.Register).Methods(http.MethodPost, http.MethodOptions) } func (a *AuthHandler) Register(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-CSRF-Token", csrf.Token(r)) }
3. 请求详情
URL: http://localhost:8080/api/v1/auth/register
Method: POST
Status code: 403 Forbidden (service worker)
URL : strict-origin-when-cross-origin
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST,OPTIONS
Access-Control-Allow-Origin: http://localhost
Content-Length: 31
Content-Type: text/plain; charset=utf-8
Date: Mon, 06 Nov 2023 11:27:57 GMT
X-Content-Type-Options: nosniff
Content-Type: application/json;charset=utf-8
Referer: http://localhost/
Sec-Ch-Ua: "Chromium";v="118", "Google Chrome";v="118", "Not=A?Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36
X-Csrf-Token: null
问题分析与修复方案
核心问题
- 令牌生成时机错误:在POST请求的
Register接口中调用csrf.Token(r)时,CSRF中间件已经在预检环节校验令牌,此时未生成有效令牌,返回null。 - 跨域预检请求处理缺失:浏览器发送OPTIONS预检请求时,未返回CSRF令牌,导致后续POST请求无法获取有效令牌。
- 中间件配置不匹配跨域场景:默认配置下CSRF令牌存储在HttpOnly Cookie中,前端无法读取,无法在请求头中携带。
修复步骤
新增令牌获取接口:创建专门的GET接口用于返回CSRF令牌,让前端先获取再发起POST请求:
func (a *AuthHandler) GetCSRFToken(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-CSRF-Token", csrf.Token(r)) w.WriteHeader(http.StatusOK) }路由注册:
mainRouter.HandleFunc("/api/v1/csrf-token", handler.GetCSRFToken).Methods(http.MethodGet, http.MethodOptions)调整CSRF中间件配置:适配跨域场景,允许前端读取Cookie中的令牌:
csrfMiddleware := csrf.Protect( []byte("qwnbjb13jbhb12j3bjbbj"), csrf.HttpOnly(false), // 关闭HttpOnly,允许前端读取Cookie csrf.SameSite(csrf.SameSiteLaxMode), // 适配跨域Cookie传递 csrf.Secure(false), // 开发环境关闭,生产环境需开启(配合HTTPS) )完善跨域响应头:确保
Access-Control-Allow-Headers包含X-CSRF-Token,避免浏览器拦截请求:w.Header().Set("Access-Control-Allow-Headers", "Content-Type,X-CSRF-Token")
前端配合操作
先请求/api/v1/csrf-token获取响应头中的X-CSRF-Token值,再在POST请求的X-CSRF-Token头中携带该值发送请求。
内容的提问来源于stack exchange,提问作者Алексей Молдованов

