Spring Boot Test:@WithMockUser不生效触发BadCredentialsException的解决
解决@WithMockUser模拟用户未生效的问题
问题场景
测试带有@PreAuthorize注解的接口方法时,使用@WithMockUser模拟用户未生效,系统抛出org.springframework.security.authentication.BadCredentialsException异常,会将127.0.0.1作为用户去数据库执行真实认证流程,需要修复以让模拟用户生效,跳过真实认证。
待测试接口方法
@PreAuthorize("hasAnyRole('ROLE_GST','ROLE_SUPER_GST','ROLE_CARGO_ASSURANCE_SUPER_USER', 'ROLE_CARGO_ASSURANCE_USER')") @RequestMapping(value = "/task/bulkupload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = { MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE }) public ResponseEntity<ApInvoicesResponse<BulkOperation>> bulkUpload(@RequestPart(value = "file") MultipartFile documentToStore) { ApInvoicesResponse<BulkOperation> successfulResponse = new ApInvoicesResponse<>(); try { BulkOperation bulkOperation = taskService.bulkUpload(documentToStore.getBytes()); successfulResponse.setData(bulkOperation); } catch (Exception e) { logger.error("apinvoicesmessage:invalid document received : {} {}",e.getMessage(),e); } return ResponseEntity.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(successfulResponse); }
原测试基类
@ExtendWith(SpringExtension.class) @SpringBootTest @AutoConfigureMockMvc @ActiveProfiles("test") public abstract class ControllerTest { @Autowired protected MockMvc mockMvc; @BeforeEach public void applySecurity() { SecurityContext context = SecurityContextHolder.createEmptyContext(); Authentication authentication = new TestingAuthenticationToken("username", "password", "ROLE_USER"); context.setAuthentication(authentication); SecurityContextHolder.setContext(context); } }
原测试方法
class ApInvoicesCmdControllerTest extends ControllerTest{ @MockBean private TaskService taskService; @Test @WithMockUser(roles={"USER"}) public void testBulkUpload_GSTUser_Successful() throws Exception { MockMultipartFile file = new MockMultipartFile("file", "test-file.txt", MediaType.TEXT_PLAIN_VALUE, "test data".getBytes()); BulkOperation bulkOperation = new BulkOperation(); Mockito.when(taskService.bulkUpload(file.getBytes())).thenReturn(bulkOperation); mockMvc.perform(MockMvcRequestBuilders.multipart("/task/bulkupload").file(file)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(jsonPath("$.data").value(bulkOperation)); verify(taskService, times(1)).bulkUpload(file.getBytes()); } }
问题原因
- 测试基类中
@BeforeEach方法手动创建并设置了TestingAuthenticationToken到SecurityContext,会覆盖@WithMockUser注解自动注入的模拟认证信息,导致注解失效。 - 测试方法中指定的
USER角色与接口要求的ROLE_GST等角色不匹配,即使注解生效也会触发权限校验失败。
修复方案
1. 删除基类手动设置认证的代码
移除ControllerTest中的@BeforeEach方法,让@WithMockUser自动管理SecurityContext,避免覆盖模拟用户信息。
修改后的测试基类:
@ExtendWith(SpringExtension.class) @SpringBootTest @AutoConfigureMockMvc @ActiveProfiles("test") public abstract class ControllerTest { @Autowired protected MockMvc mockMvc; }
2. 调整测试方法的模拟角色
@WithMockUser的roles参数会自动添加ROLE_前缀,因此直接指定接口要求的角色名称即可匹配权限校验规则。
修改后的测试方法:
class ApInvoicesCmdControllerTest extends ControllerTest { @MockBean private TaskService taskService; @Test @WithMockUser(roles = {"GST"}) public void testBulkUpload_GSTUser_Successful() throws Exception { MockMultipartFile file = new MockMultipartFile("file", "test-file.txt", MediaType.TEXT_PLAIN_VALUE, "test data".getBytes()); BulkOperation bulkOperation = new BulkOperation(); Mockito.when(taskService.bulkUpload(file.getBytes())).thenReturn(bulkOperation); mockMvc.perform(MockMvcRequestBuilders.multipart("/task/bulkupload").file(file)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(jsonPath("$.data").exists()); verify(taskService, times(1)).bulkUpload(file.getBytes()); } }
额外说明
@AutoConfigureMockMvc会自动集成Spring Security的测试支持,确保@WithMockUser等注解正确生效,无需手动管理SecurityContext。- 若需在多个测试中复用相同模拟用户,可直接在测试基类上添加
@WithMockUser注解,无需在每个测试方法重复声明。
内容的提问来源于stack exchange,提问作者Abhishek
相关产品推荐
相关产品推荐

