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

使用SpaCsrfTokenRequestHandler时MockMvc的csrf() API测试失效问题

问题分析与解决方案

问题核心

使用Spring Boot推荐的SpaCsrfTokenRequestHandler实现双重提交Cookie模式的CSRF防护时,MockMvc的csrf() API无法适配该场景:手动设置xsrf-token Cookie和x-xsrf-token Header的测试能正常通过,但调用with(csrf().asHeader())的两种写法均触发InvalidCsrfTokenException。

原因拆解

MockMvc默认的CsrfRequestPostProcessor是为Spring Security传统CSRF配置设计的,和SpaCsrfTokenRequestHandler存在两处关键不匹配:

  • 默认使用的Header名称是X-CSRF-TOKEN,而非SpaCsrfTokenRequestHandler要求的x-xsrf-token
  • 仅会将生成的CSRF Token放入Header或请求参数,不会同步设置对应的xsrf-token Cookie,违背双重提交Cookie模式「Cookie与Header值必须一致」的核心要求

有效测试代码(手动实现)

mockMvc
    .perform(post("/api/some-endpoint")
        .cookie(new Cookie("xsrf-token", "abc"))
        .header("x-xsrf-token", "abc")
        .contentType(MediaType.APPLICATION_JSON_VALUE)
        .content("Some body content")
     .andExpect(status().is2xxSuccessful())
     .andReturn();

无效代码及异常

无效代码1

mockMvc
    .perform(post("/api/some-endpoint")
        .cookie(new Cookie("xsrf-token", "abc"))
        .with(csrf().asHeader())
        .contentType(MediaType.APPLICATION_JSON_VALUE)
        .content("Some body content")
     .andExpect(status().is2xxSuccessful())
     .andReturn();

异常信息:

org.springframework.security.web.csrf.InvalidCsrfTokenException: Invalid CSRF Token 'J68ItVNNwwdMhD-qLrdkGN8ox2fAk73IO4md1BNIGvIl-IFlQZY8hGp59GRhtAbOG5pQKe0Z6galoInlX-z-7XJxfsoVy7AD' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.

无效代码2

mockMvc
    .perform(post("/api/some-endpoint")
        .with(csrf().asHeader())
        .contentType(MediaType.APPLICATION_JSON_VALUE)
        .content("Some body content")
     .andExpect(status().is2xxSuccessful())
     .andReturn();

异常信息:

org.springframework.security.web.csrf.InvalidCsrfTokenException: Invalid CSRF Token '1SKNwUSrMfPyT9Wp4A1hP8zjY-31exqNdqGvz4avElN50TTQthDp8HOcAZDffu2cgSBVC__UTtTCQnigEJmcquTKczdLtwHg' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.

解决方案

方案1:自定义CSRF请求处理器

编写适配SpaCsrfTokenRequestHandler的MockMvc请求处理器,自动生成匹配的Cookie和Header:

private RequestPostProcessor spaCsrf() {
    return request -> {
        String csrfToken = UUID.randomUUID().toString();
        // 设置SpaCsrfTokenRequestHandler要求的Cookie
        request.setCookies(new Cookie("xsrf-token", csrfToken));
        // 设置对应的Header
        request.addHeader("x-xsrf-token", csrfToken);
        return request;
    };
}

使用方式:

mockMvc
    .perform(post("/api/some-endpoint")
        .with(spaCsrf())
        .contentType(MediaType.APPLICATION_JSON_VALUE)
        .content("Some body content")
     .andExpect(status().is2xxSuccessful())
     .andReturn();

方案2:复用手动配置逻辑并封装

将手动设置Cookie和Header的逻辑封装成工具方法,避免重复代码:

private MockHttpServletRequestBuilder withSpaCsrf(MockHttpServletRequestBuilder builder) {
    String csrfToken = UUID.randomUUID().toString();
    return builder.cookie(new Cookie("xsrf-token", csrfToken))
                  .header("x-xsrf-token", csrfToken);
}

使用方式:

mockMvc
    .perform(withSpaCsrf(post("/api/some-endpoint"))
        .contentType(MediaType.APPLICATION_JSON_VALUE)
        .content("Some body content")
     .andExpect(status().is2xxSuccessful())
     .andReturn();

方案3:全局配置MockMvc适配SpaCsrfTokenRequestHandler

通过自定义CsrfTokenRepository,让MockMvc默认适配SpaCsrf的命名规则:

@Bean
public MockMvc mockMvc(WebApplicationContext context) {
    CsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
    csrfTokenRepository.setCookieName("xsrf-token");
    csrfTokenRepository.setHeaderName("x-xsrf-token");

    return MockMvcBuilders.webAppContextSetup(context)
            .apply(SecurityMockMvcConfigurers.springSecurity())
            .defaultRequest(get("/").with(csrf().csrfTokenRepository(csrfTokenRepository)))
            .build();
}

配置完成后,直接使用with(csrf().asHeader())即可自动匹配正确的Cookie和Header名称。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 11:33:24