You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React首次渲染获取CSRF Token后并发POST请求返回403如何解决

问题根因

你遇到的403问题本质是异步竞态:获取CSRF Token的GET请求是异步执行的,而业务组件的POST请求和这个GET请求在首次渲染时并行触发,POST请求发送时Token还没获取完成,自然不会携带X-CSRF-Token头。

解决方案

下面给三个可落地的方案,按需选择:

方案1:加加载状态阻塞子组件渲染(改造成本最低)

在FetchProvider中新增加载状态,只有CSRF Token获取完成后才渲染子组件,从根源上避免提前发请求。

import axios from "axios";
import { createContext, useEffect, useState } from "react";

const FetchContext = createContext();
const { Provider } = FetchContext;

const FetchProvider = ({ children }) => {
    const [isCsrfLoaded, setIsCsrfLoaded] = useState(false)
    const publicAxios = axios.create({ baseURL: '/api' })
    const authAxios = axios.create({ baseURL: '/api' })
    authAxios.interceptors.response.use(
        response => response,
        error => Promise.reject(error)
    );

    useEffect(() => {
        const getCsrfToken = async () => {
            try {
                const { data } = await publicAxios.get('/csrf-token');
                publicAxios.defaults.headers['X-CSRF-Token'] = data.csrfToken;
                authAxios.defaults.headers['X-CSRF-Token'] = data.csrfToken;
            } catch (err) {
                console.log(err)
            } finally {
                setIsCsrfLoaded(true)
            }
        };
        getCsrfToken();
    }, [authAxios, publicAxios]);

    // 未加载完成时可以返回自定义加载占位符,避免白屏
    if (!isCsrfLoaded) return <div>加载中...</div>

    return (
        <Provider value={{ authAxios, publicAxios }}>
            {children}
        </Provider>
    );
};

export { FetchContext, FetchProvider }

这个方案不需要改任何业务组件代码,适合快速修复问题。

方案2:加请求拦截器动态补全Token(体验更优)

不需要阻塞渲染,给axios实例加请求拦截器,每次发请求前自动判断有没有Token,没有就先获取再发送请求,支持并行请求自动合并Token获取操作,不会重复发/csrf-token请求。

import axios from "axios";
import { createContext, useMemo } from "react";

const FetchContext = createContext();
const { Provider } = FetchContext;

// 缓存Token获取的Promise,避免并行请求重复触发接口调用
let csrfTokenPromise = null
const getCsrfToken = async (publicAxios) => {
    if (csrfTokenPromise) return csrfTokenPromise
    csrfTokenPromise = publicAxios.get('/csrf-token').then(res => res.data.csrfToken)
    try {
        return await csrfTokenPromise
    } finally {
        csrfTokenPromise = null
    }
}

const FetchProvider = ({ children }) => {
    const publicAxios = useMemo(() => axios.create({ baseURL: '/api' }), [])
    const authAxios = useMemo(() => {
        const instance = axios.create({ baseURL: '/api' })
        // 原有响应拦截器保留
        instance.interceptors.response.use(
            response => response,
            error => Promise.reject(error)
        )
        // 新增请求拦截器,动态加CSRF头
        instance.interceptors.request.use(
            async (config) => {
                // 跳过GET请求和csrf-token接口本身,避免死循环
                if (config.method === 'get' || config.url === '/csrf-token') return config
                // 没有Token就先获取
                if (!instance.defaults.headers['X-CSRF-Token']) {
                    const token = await getCsrfToken(publicAxios)
                    instance.defaults.headers['X-CSRF-Token'] = token
                    publicAxios.defaults.headers['X-CSRF-Token'] = token
                }
                config.headers['X-CSRF-Token'] = instance.defaults.headers['X-CSRF-Token']
                return config
            },
            error => Promise.reject(error)
        )
        return instance
    }, [publicAxios])

    // publicAxios如果有POST请求需求,也可以加同样的请求拦截器

    return (
        <Provider value={{ authAxios, publicAxios }}>
            {children}
        </Provider>
    );
};

export { FetchContext, FetchProvider }

这个方案不需要加全局加载状态,用户无感,适合对首屏加载速度要求高的场景。

方案3:后端配合从Cookie读取Token(维护成本最低)

如果后端支持,可以调整CSRF校验逻辑:生成CSRF Token时直接写入HttpOnly=false的Cookie,前端不需要手动拿Token设置头,后端直接从Cookie中读取对应的CSRF Token校验即可,前端完全不需要处理Token相关逻辑,从根源上避免这个问题。


内容的提问来源于stack exchange,提问作者Manishkumar Adesara

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 08:06:03