Spring Boot测试返回401但应用返回403的问题排查与解决
问题原因分析
实际应用和MockMvc测试行为不一致的核心原因是错误页面的处理流程差异:
- 生产环境中,登录接口返回401后,Spring Boot会自动转发到
/error端点;而你的Security配置中/error端点不允许匿名访问,所以触发Http403ForbiddenEntryPoint,最终返回403。 - MockMvc默认不会自动处理这种错误转发逻辑,登录失败后直接返回原始的401响应,不会走到
/error的拦截环节。
解决方案
方法1:让MockMvc模拟错误页面转发流程
修改MockMvc的配置,添加错误处理的模拟,让它和生产环境一致:
@SpringBootTest @AutoConfigureMockMvc class LoginTest { @Autowired lateinit var mockMvc: MockMvc @Test fun `test login returns 403`() { mockMvc.perform(MockMvcRequestBuilders.post("/login")) .andExpect(status().isForbidden) .andDo(MockMvcResultHandlers.print()) } }
如果还是不行,手动配置MockMvc启用错误调度:
@Autowired lateinit var webApplicationContext: WebApplicationContext lateinit var mockMvc: MockMvc @BeforeEach fun setup() { mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) .apply(springSecurity()) .setDispatcherServletCustomizer { servlet -> servlet.setThrowExceptionIfNoHandlerFound(true) servlet.setDispatchOptionsRequest(true) servlet.setDispatchTraceRequest(true) } .build() }
方法2:调整Security配置,直接在登录接口返回403
如果不想依赖/error的转发逻辑,可以修改SecurityConfig,让登录失败后直接返回403,而不是先返回401再转发:
@Configuration @EnableWebSecurity class SecurityConfig { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http .authorizeHttpRequests { auth -> auth.anyRequest().authenticated() } .formLogin { form -> form.loginProcessingUrl("/login") .failureHandler { request, response, exception -> response.sendError(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.reasonPhrase) } } .exceptionHandling { exceptions -> exceptions.authenticationEntryPoint(Http403ForbiddenEntryPoint()) } return http.build() } }
这样不管是生产环境还是MockMvc测试,都会直接返回403,行为一致。
方法3:使用TestRestTemplate模拟真实请求
如果MockMvc的模拟还是有差异,可以用TestRestTemplate来测试,它会完全模拟HTTP请求流程,包括错误页面转发:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class LoginTest { @Autowired lateinit var restTemplate: TestRestTemplate @Test fun `test login returns 403`() { val response = restTemplate.postForEntity("/login", null, String::class.java) assertThat(response.statusCode).isEqualTo(HttpStatus.FORBIDDEN) } }
内容的提问来源于stack exchange,提问作者Niebioh
相关产品推荐
相关产品推荐

