Spring WebMvcTest使用@WithMockUser配置角色仍返回HTTP403
问题根因
返回403的核心原因是Spring Security默认开启的CSRF防护拦截了请求,和用户角色配置无关:
- 当
@WebMvcTest未加载自定义SecurityFilterChainBean时,会自动启用Spring Security默认安全规则:仅放行GET/HEAD/TRACE/OPTIONS方法的CSRF校验,其余HTTP方法必须携带合法CSRF令牌才能通过过滤器链 - DELETE请求未携带CSRF令牌时,会在到达Controller层之前被CSRF过滤器直接拦截返回403,对应日志中
Handler: Type = null的现象,@WithMockUser配置的角色信息不会参与这一阶段的校验。
GET方法测试可正常通过、手动调用接口正常的现象也可佐证这一点:生产环境自定义的SecurityFilterChain通常会针对无状态REST接口关闭CSRF防护,手动调用不会触发CSRF校验,但@WebMvcTest未加载该自定义配置,使用的是默认开启CSRF的安全规则。
轻量化解决方案
方案1:测试请求附加CSRF令牌(零额外配置,优先选用)
无需新增任何配置类、无需修改测试上下文加载规则,仅需在构造MockMvc请求时通过Spring Security测试工具内置的csrf()请求处理器附加合法令牌即可,改动量最小:
// 新增静态导入 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; @Test @WithMockUser(roles={"USER","ADMIN"}) void shouldReturn204ForDeleteProduct() throws Exception { var productId = 1; mockMvc.perform(delete(SINGLE_RESOURCE_ENDPOINT_URL, productId) .with(csrf()) // 附加合法CSRF令牌,通过CSRF过滤器校验 ) .andExpect(status().isNoContent()); }
所有POST/PUT/DELETE/PATCH等非GET方法的测试用例,都可以通过追加.with(csrf())的方式快速通过CSRF校验,不会引入额外的上下文加载开销。
方案2:测试类内嵌最小安全配置(无额外类文件依赖)
如果对应REST接口本身是无状态服务,生产环境也会关闭CSRF防护,可以直接在测试类内部内嵌一个最小化的安全配置,不需要引入项目中完整的Spring Security配置:
// 新增导入 import org.springframework.context.annotation.Import; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; import org.springframework.context.annotation.Configuration; @ExtendWith(SpringExtension.class) @ContextConfiguration @WebMvcTest({ProductInformationController.class, CommonControllerAdvice.class}) @Import(ProductInformationControllerTest.TestSecurityConfig.class) // 导入内嵌的测试安全配置 class ProductInformationControllerTest { // 内嵌测试专用安全配置,无需新建独立类文件 @Configuration @EnableWebSecurity static class TestSecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) // 测试环境关闭CSRF校验 .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()); // 保持基础认证规则 return http.build(); } } private static final String SINGLE_RESOURCE_ENDPOINT_URL = "/product/{id}"; @Autowired MockMvc mockMvc; @MockBean ProductInformationService service; @Test @WithMockUser(roles={"USER","ADMIN"}) void shouldReturn204ForDeleteProduct() throws Exception { var productId = 1; mockMvc.perform(delete(SINGLE_RESOURCE_ENDPOINT_URL, productId)) .andExpect(status().isNoContent()); } // 其余测试方法保持不变 }
该配置仅加载测试必需的安全规则,不会引入项目中完整安全配置的多余依赖,上下文加载成本极低。
内容的提问来源于stack exchange,提问作者void
相关产品推荐
相关产品推荐

