跨端口独立部署React SPA如何对接Go端gorilla/csrf解决CSRF令牌取值为undefined的问题
问题根因
- 你当前前端从DOM取
gorilla.csrf.Token的逻辑,是适配Go后端直接渲染HTML模板的场景,React独立跨域部署时页面根本不存在对应的隐藏DOM元素,取值自然为undefined - 你虽然在后端响应中设置了
X-CSRF-Token自定义头,但跨域场景下未配置CORS暴露规则,前端JS没有权限读取这个响应头的值 - gorilla/csrf中间件使用默认配置时,跨域适配的Cookie参数缺失,后续带令牌的请求会因为Cookie被浏览器拦截导致校验失败
- 跨域预检OPTIONS请求未被正确放行,跨域请求流程从源头就无法打通
后端配置调整
1. 调整中间件加载顺序
CORS中间件必须放在路由链最外层,优先级高于CSRF中间件,否则预检请求会被CSRF逻辑拦截
参考调整后的路由配置:
func Route(app *App) *mux.Router { r := mux.NewRouter() // 1. 自定义CORS中间件,放在最外层 corsMiddleware := func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 配置允许的前端跨域源 w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000") // 允许跨域请求携带Cookie w.Header().Set("Access-Control-Allow-Credentials", "true") // 允许前端传递的请求头 w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token") // 允许的HTTP方法 w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") // 暴露X-CSRF-Token头给前端JS读取 w.Header().Set("Access-Control-Expose-Headers", "X-CSRF-Token") // 直接放行OPTIONS预检请求,不进入后续校验逻辑 if r.Method == "OPTIONS" { w.WriteHeader(http.StatusNoContent) return } next.ServeHTTP(w, r) }) } // 2. 调整gorilla/csrf中间件的跨域适配参数 csrfMiddleware := csrf.Protect( []byte("32-byte-long-auth-key"), csrf.Secure(false), // 本地HTTP开发用false,生产HTTPS环境改true csrf.HttpOnly(true), // 保持CookieHttpOnly属性,降低XSS风险 csrf.SameSite(http.SameSiteLaxMode), // 本地开发适配,生产跨域场景改SameSiteNoneMode csrf.Path("/"), // 让CSRF Cookie对所有路由生效 ) // 3. 全局写入CSRF响应头的中间件 csrfHeaderMiddleware := func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-CSRF-Token", csrf.Token(r)) next.ServeHTTP(w, r) }) } api := r.PathPrefix("/api").Subrouter() // 按顺序应用中间件:CORS -> CSRF防护 -> 写入CSRF响应头 api.Use(corsMiddleware, csrfMiddleware, csrfHeaderMiddleware) // 原有业务路由保持不变 // user api.HandleFunc("/users/{id:[0-9]+}", app.tokenVerifyMiddleWare(app.userGetHandler)).Methods("GET", "OPTIONS") api.HandleFunc("/users", app.signupHandler).Methods("POST", "OPTIONS") api.HandleFunc("/users/{id:[0-9]+}", app.userDeleteHandler).Methods("DELETE", "OPTIONS") api.HandleFunc("/users/{id:[0-9]+}", app.userUpdateHandler).Methods("PUT", "OPTIONS") // like api.HandleFunc("/users/{id:[0-9]+}/likes", app.tokenVerifyMiddleWare(app.likeGetALLHandler)).Methods("GET", "OPTIONS") api.HandleFunc("/users/{id:[0-9]+}/likes", app.tokenVerifyMiddleWare(app.likePostHandler)).Methods("POST", "OPTIONS") api.HandleFunc("/users/{id:[0-9]+}/likes/{id:[0-9]+}", app.tokenVerifyMiddleWare(app.likeDeleteHandler)).Methods("DELETE", "OPTIONS") // auth api.HandleFunc("/login", app.loginHandler).Methods("POST", "OPTIONS") api.HandleFunc("/logout", app.tokenVerifyMiddleWare(app.logoutHandler)).Methods("POST", "OPTIONS") api.HandleFunc("/refresh_token", app.refreshTokenHandler).Methods("POST", "OPTIONS") return r }
前端逻辑调整
- 删除原有从DOM取
gorilla.csrf.Token的无效代码 - 令牌仅存在内存中,不要存在localStorage/sessionStorage,避免XSS风险
- 必须开启Axios的
withCredentials配置,否则跨域请求无法自动携带CSRF Cookie,gorilla/csrf校验必然失败 - 页面初始化时先调用一次GET类公开接口(GET请求不会触发CSRF校验),从响应头读取CSRF令牌
- 所有需要CSRF校验的请求(POST/PUT/DELETE等),自动在请求头携带读取到的令牌
参考调整后的前端代码:
// 内存存储CSRF令牌 let csrfToken = null // 初始化Axios实例 const instance = axios.create({ baseURL: "http://localhost:8080/api/", timeout: 1000, withCredentials: true, // 必须开启跨域凭证携带 }) // 初始化CSRF令牌:页面加载时调用 async function initCsrfToken() { if (csrfToken) return // 调用任意公开GET接口,gorilla/csrf默认不对GET请求做校验 const resp = await instance.get("/health") // 从响应头读取令牌 csrfToken = resp.headers["x-csrf-token"] } // 请求拦截器:自动给需要CSRF校验的请求加头 instance.interceptors.request.use(config => { const needCsrf = ["POST", "PUT", "DELETE", "PATCH"].includes(config.method.toUpperCase()) if (needCsrf && csrfToken) { config.headers["X-CSRF-Token"] = csrfToken } return config }) // 页面加载完成后先初始化CSRF令牌,再执行业务逻辑 initCsrfToken().then(() => { // 后续正常调用接口即可 })
注意事项
- 生产环境部署时,必须将
csrf.Secure设为true,同时把SameSite配置为SameSiteNoneMode,否则跨域Cookie会被浏览器拦截 - 首次加载页面时必须先完成CSRF令牌初始化,再发送POST类请求,否则会因为没有令牌和Cookie被403拦截
- 不要修改CSRF Cookie的HttpOnly属性,全程保持前端不直接读取Cookie值,浏览器自动携带即可,降低XSS攻击风险
内容的提问来源于stack exchange,提问作者マットmatto
相关产品推荐
相关产品推荐

