Spring Boot MockMvc单元测试返回403 Forbidden求助
问题:POST接口单元测试返回403 Forbidden排查
我为PDFTaggerController的POST接口编写了单元测试,但测试返回403 Forbidden,以下是相关代码、日志及Spring Security配置,请帮忙排查问题。
控制器代码
接口通过@RequestPart("FileContent")接收PDF文件:
@RequestMapping(value = "/v1/getFillableFormElements", method = RequestMethod.POST) public @ResponseBody HttpEntity getFillableFormElements(@RequestPart("FileContent") @Valid MultipartFile FileContent, @RequestParam int pageNo) throws IOException { FormElementsService formElementsService = new FormElementsServiceImpl(); formElementsService.process(pdfFilePath, returnFilePath); return setReturnHttpHeaders(returnFilePath); }
单元测试代码
@WebMvcTest @ContextConfiguration(classes = PDFTaggerController.class) public class PDFTaggerControllerTests { @MockBean PDFTaggerController controller; @Autowired private MockMvc mockMvc; @Autowired private WebApplicationContext webApplicationContext; @Before() public void setup() { mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); } @Test public void getFillableFormElements() throws Exception { File file = new File("C:\\Downloads\\sample.pdf"); FileInputStream fileInputStream = new FileInputStream(file); MockMultipartFile firstFile = new MockMultipartFile("file", file.getName(), "multipart/.pdf", fileInputStream); doNothing().when(retrieveFillableFormElementsService).process(anyString(), anyString()); mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/getFillableFormElements") .file(firstFile) .param("pageNo", "0")) .andExpect(status().isOk()); } }
测试输出日志
MockHttpServletRequest: HTTP Method = POST Request URI = /api/v1/getFillableFormElements Parameters = {pageNo=[0]} Headers = [Content-Type:"multipart/form-data;charset=UTF-8"] Body = null Session Attrs = {org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN=org.springframework.security.web.csrf.DefaultCsrfToken@5d08976a} Handler: Type = null Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 403 Error message = Forbidden Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"] Content type = null Body = Forwarded URL = null Redirected URL = null Cookies = []
Spring Security配置代码
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationEntryPoint authenticationEntryPoint; public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/api/v1/status"); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(). authorizeRequests().antMatchers("*").permitAll() .antMatchers("/pingnot").anonymous() .antMatchers("/**").authenticated().and().exceptionHandling().and().httpBasic() .authenticationEntryPoint(authenticationEntryPoint).and().sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.authorizeRequests().antMatchers("/swagger-ui.html").authenticated().and() .formLogin().permitAll().and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); http.addFilterBefore(new BasicAuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class); } }
问题排查及解决方案
1. 测试用例的核心问题
- Mock了控制器本身:
@MockBean PDFTaggerController controller会替换掉Spring容器中真实的控制器实例,导致请求找不到处理器(日志中Handler: Type = null)。测试控制器时,应该Mock控制器依赖的服务(如FormElementsService),而非控制器本身。 - 请求参数名不匹配:测试中
MockMultipartFile的参数名是"file",但控制器中@RequestPart指定的是"FileContent",参数名不匹配会导致请求无法正确绑定参数。 - 未正确Mock依赖服务:测试中
retrieveFillableFormElementsService未定义,应该Mock控制器中使用的FormElementsService。
修正后的测试用例:
@WebMvcTest(PDFTaggerController.class) public class PDFTaggerControllerTests { @Autowired private MockMvc mockMvc; // Mock控制器依赖的服务,而非控制器本身 @MockBean private FormElementsService formElementsService; @Test public void getFillableFormElements() throws Exception { File file = new File("C:\\Downloads\\sample.pdf"); FileInputStream fileInputStream = new FileInputStream(file); // 修正参数名为"FileContent",与控制器一致 MockMultipartFile pdfFile = new MockMultipartFile("FileContent", file.getName(), "application/pdf", fileInputStream); // Stub服务方法 doNothing().when(formElementsService).process(anyString(), anyString()); mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/getFillableFormElements") .file(pdfFile) .param("pageNo", "0")) .andExpect(status().isOk()); } }
2. Spring Security配置的冲突问题
- 规则顺序与冲突:Security规则是从上到下匹配,先匹配的生效。你先配置了
antMatchers("*").permitAll()(匹配一级路径,如/api/v1),随后又配置antMatchers("/**").authenticated()(匹配所有路径),导致/api/v1/getFillableFormElements会匹配到"/**"规则,要求认证,返回403。 - 重复配置导致的混乱:多次调用
http.authorizeRequests(),且同时设置了SessionCreationPolicy.STATELESS(无状态,适合REST)和SessionCreationPolicy.IF_REQUIRED(适合表单登录),配置冲突。 - 冗余过滤器:手动添加
BasicAuthenticationFilter是多余的,httpBasic()已经会自动添加该过滤器。
修正后的Security配置:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationEntryPoint authenticationEntryPoint; @Override public void configure(WebSecurity web) throws Exception { // 放行静态资源或无需认证的接口 web.ignoring().antMatchers("/api/v1/status"); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() // 统一配置授权规则,避免重复调用authorizeRequests() .authorizeRequests() // 放行测试的PDF接口(根据需求调整) .antMatchers("/api/v1/getFillableFormElements").permitAll() .antMatchers("/pingnot").anonymous() .antMatchers("/swagger-ui.html").authenticated() // 其他所有路径需要认证 .anyRequest().authenticated() .and() .httpBasic() .authenticationEntryPoint(authenticationEntryPoint) .and() // 统一设置会话策略,REST接口建议用STATELESS .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); } }
3. 测试时的认证处理
如果不想修改Security配置放行接口,可以在测试方法上添加@WithMockUser注解模拟认证用户:
@Test @WithMockUser(username = "test", roles = "USER") public void getFillableFormElements() throws Exception { // 测试代码不变 }
内容的提问来源于stack exchange,提问作者Nitish Kumar
相关产品推荐
相关产品推荐

