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

Spring Security 6.2.1+Vue3(axios) POST请求CSRF令牌无效(403)求助

Spring + Vue SPA CSRF防护失效排查方案

问题背景

开发Spring+Vue单页应用时,CSRF防护无法正常启用。已参考相关方案配置,但POST请求始终无法识别CSRF令牌(尝试过_csrf、X-XSRF-TOKEN、X-XSRF、X-CSRF-TOKEN等名称),响应头已包含SET-COOKIES字段携带令牌,但验证始终失败。Axios自动携带和手动添加令牌均无效。

后端Spring Security配置代码

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
                .csrf((csrf) -> csrf
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                        .csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler())
                )
                .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);
        return http.build();
    }
}

final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
    private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
        /*
         * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
         * the CsrfToken when it is rendered in the response body.
         */
        this.delegate.handle(request, response, csrfToken);
    }

    @Override
    public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
        /*
         * If the request contains a request header, use CsrfTokenRequestAttributeHandler
         * to resolve the CsrfToken. This applies when a single-page application includes
         * the header value automatically, which was obtained via a cookie containing the
         * raw CsrfToken.
         */
        if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
            return super.resolveCsrfTokenValue(request, csrfToken);
        }
        /*
         * In all other cases (e.g. if the request contains a request parameter), use
         * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
         * when a server-side rendered form includes the _csrf request parameter as a
         * hidden input.
         */
        return this.delegate.resolveCsrfTokenValue(request, csrfToken);
    }
}

final class CsrfCookieFilter extends OncePerRequestFilter {
    private static final Logger logger = LoggerFactory.getLogger(CsrfCookieFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        CsrfToken csrfToken = (CsrfToken) request.getAttribute("X-CSRF-TOKEN");
        // Render the token value to a cookie by causing the deferred token to be loaded
        csrfToken.getToken();

        filterChain.doFilter(request, response);
    }
}

前端Axios请求代码

export async function validateObservationsId(producer_id: string, dataset_id: string, document_ids: string[]): Boolean[] {
    const url = `${myBaseUrl}/download/observation/${producer_id}/${dataset_id}/exists`;
    // 从Cookie获取CSRF令牌
    const csrfToken = document.cookie
        .split('; ')
        .find(row => row.startsWith('XSRF-TOKEN='))
        ?.split('=')[1];
    console.log("CSRF Token: ", csrfToken);
    try {
        let response = await axios.post(
            url,
            document_ids,
            {
            headers: { 'X-CSRF-TOKEN': csrfToken }
        }
        );
        return response.data;
    } catch (error) {
        console.error('Error validating observation IDs', error);
        return [];
    }
}

排查与解决方案

1. 修复CsrfCookieFilter中的令牌属性名错误

当前CsrfCookieFilter中硬编码的请求属性名X-CSRF-TOKEN不正确,Spring Security默认将CsrfToken存储在CsrfToken.class.getName()对应的属性中,修改代码:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
    if (csrfToken != null) {
        csrfToken.getToken();
    }
    filterChain.doFilter(request, response);
}

2. 对齐令牌Cookie与请求头名称

CookieCsrfTokenRepository默认生成的Cookie名称是XSRF-TOKEN,对应的请求头应为X-XSRF-TOKEN(而非X-CSRF-TOKEN)。

方案一:修改前端请求头

headers: { 'X-XSRF-TOKEN': csrfToken }

方案二:后端显式配置头名称

csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()
        .headerName("X-CSRF-TOKEN"))

3. 确保Axios自动携带令牌的配置正确

Axios默认读取XSRF-TOKENCookie并添加X-XSRF-TOKEN请求头,但需确保:

  • 跨域场景下开启withCredentials
  • 配置与后端一致的令牌名称

添加Axios全局配置:

axios.defaults.withCredentials = true;
axios.defaults.xsrfHeaderName = 'X-XSRF-TOKEN';
axios.defaults.xsrfCookieName = 'XSRF-TOKEN';

4. 验证请求实际发送的信息

通过浏览器开发者工具(Network标签)确认:

  • 请求头中是否包含正确的CSRF令牌字段
  • 令牌值与Cookie中的XSRF-TOKEN是否一致
  • 跨域请求是否携带了Cookie

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 03:02:15