
在 Spring Boot 3 + Spring Security 6 环境下,单元测试无需全局禁用 CSRF;推荐通过 MockMvcRequestPostProcessors.csrf() 为特定请求显式注入合法令牌,既保持生产配置完整性,又确保测试通过。
在 spring boot 3 + spring security 6 环境下,单元测试无需全局禁用 csrf;推荐通过 `mockmvcrequestpostprocessors.csrf()` 为特定请求显式注入合法令牌,既保持生产配置完整性,又确保测试通过。
在 Spring Boot 3 中,CSRF 保护默认启用且深度集成于 SecurityFilterChain 流程中。许多开发者在编写 @WebMvcTest 或集成测试时,因未处理 CSRF 令牌而遭遇 403 Forbidden(状态变更请求)或 406 Not Acceptable(如您的 GET 请求返回异常)等错误——这并非认证失败,而是 Spring Security 的 CsrfFilter 拦截了未携带有效令牌的请求。
⚠️ 关键误区警示:
- ❌ 不应为测试环境单独创建一套“关闭 CSRF”的
@TestConfiguration并覆盖主安全配置——这会掩盖真实安全行为,降低测试可信度; - ❌ 避免在测试中使用
@AutoConfigureTestDatabase或@MockBean干预SecurityFilterChain,易引发 Bean 冲突或配置失效; - ✅ 正确做法是:保持生产级安全配置不变,在测试请求层面按需提供 CSRF 支持。
✅ 推荐方案:使用 SecurityMockMvcRequestPostProcessors.csrf()
Spring Security 提供了开箱即用的测试工具类 SecurityMockMvcRequestPostProcessors(位于 spring-security-test 依赖中),其 csrf() 方法可为任意 MockMvc 请求自动注入一个有效的、服务器可验证的 CSRF 令牌(基于内存 TokenRepository),完全模拟真实表单提交行为。
▶ 示例:修复您的测试用例
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Test
@WithMockUser(authorities = "user=admin")
void asAdmin_canGetPhoto() throws Exception {
mockMvc.perform(
get("/photo")
.with(csrf()) // ← 关键:显式添加 CSRF 支持
).andExpect(status().isOk());
}
? 提示:
csrf()默认生成符合CookieCsrfTokenRepository或HttpSessionCsrfTokenRepository行为的令牌,与您生产环境配置完全兼容。若自定义了CsrfTokenRepository(如使用 JWT + Cookie 方案),该处理器仍能正确适配。
? 补充:针对不同 HTTP 方法的适配建议
| 请求类型 | 是否需要 .with(csrf())
|
说明 |
|---|---|---|
GET, HEAD, OPTIONS, TRACE
|
❌ 通常不需要 | 这些幂等方法默认不受 CSRF 保护(除非显式配置) |
POST, PUT, DELETE, PATCH
|
✅ 必须添加 | 所有非幂等状态变更请求均受默认防护拦截 |
带 @RequestBody 的 JSON API |
✅ 强烈建议添加 | 即使无表单,Spring Security 仍校验令牌(尤其当 Content-Type: application/json 且未禁用 CSRF) |
? 确保依赖完整(Maven)
<dependency><groupid>org.springframework.security</groupid><artifactid>spring-security-test</artifactid><scope>test</scope></dependency>
✅ Spring Boot 3.x 默认已传递引入该模块,但建议显式声明以明确语义并避免版本漂移。
? 进阶技巧:批量应用 CSRF(适用于多请求场景)
若一个测试类中大量请求需 CSRF,可提取为 @BeforeEach 公共逻辑:
@BeforeEach
void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(springSecurity())
.alwaysDo(result -> {
// 可选:记录每次请求的 CSRF token 值用于调试
CsrfToken token = (CsrfToken) result.getRequest().getAttribute("_csrf");
System.out.println("Used CSRF token: " + token.getToken());
})
.build();
}
✅ 总结:安全与可测性的平衡之道
-
不妥协安全性:生产环境的
csrf().enable()保持原样,零配置修改; -
不牺牲可测性:测试层仅通过
with(csrf())注入令牌,轻量、精准、可追溯; - 不破坏契约一致性:所有测试运行在与生产一致的过滤器链下,真正验证“带 CSRF 的系统是否按预期工作”。
这种模式不仅解决了 403/406 报错问题,更体现了现代 Spring 安全测试的核心理念:测试的是“如何安全地工作”,而非“如何绕过安全”。











