Spring Boot单元测试中如何Mock Spring Security Cookie会话?
我在Spring Boot REST API中通过AuthenticationManager实现了HTTP Cookie认证,控制器提供的REST接口可通过Spring Security Cookie会话对/api/auth/signin资源完成认证。现在要为对应/signin的authenticateUser方法编写单元测试,但Mock时遇到问题,测试返回403错误。
测试代码
@ExtendWith(SpringExtension.class) @ContextConfiguration(classes=Application.class) @WebMvcTest(AuthController.class) public class AuthControllerTest { @MockBean UserRepository userRepository; @MockBean AuthenticationManager authenticationManager; @MockBean private UserDetailsServiceImpl userDetailsServiceImpl; @Autowired private MockMvc mockMvc; private static UserDetailsImpl dummy; @MockBean private JwtUtils jwtUtil; @Autowired WebApplicationContext webApplicationContext ; private ResponseCookie cookies; @BeforeEach public void setUp() { dummy = new UserDetailsImpl(10L,"test1","test1@mail.com","123456",new ArrayList<>()); // loginRequest未定义,此处会直接报错 Authentication authentication = authenticationManager .authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); SecurityContextHolder.getContext().setAuthentication(authentication); // 注释残留语法错误 // UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();*/ cookies = jwtUtil.generateJwtCookie(dummy) ; } @Test @DisplayName("POST /signin") void authenticateUser() throws Exception { LoginRequest authenticationRequest = new LoginRequest("mod", "123456") ; String jsonRequest = asJsonString(authenticationRequest); RequestBuilder request = MockMvcRequestBuilders .post("/api/auth/signin") .content(jsonRequest) .contentType(MediaType.APPLICATION_JSON_VALUE) .accept(MediaType.APPLICATION_JSON); Authentication auth = Mockito.mock(Authentication.class); Mockito.when(auth.getName()).thenReturn("authName"); auth.setAuthenticated(true); Mockito.when(auth.isAuthenticated()).thenReturn(true); // Mock参数匹配错误,实际传入的是UsernamePasswordAuthenticationToken实例 Mockito.when(authenticationManager.authenticate(auth)).thenReturn(auth); Mockito.when(jwtUtil.generateJwtCookie(dummy)).thenReturn(cookies); Mockito.when(userDetailsServiceImpl.loadUserByUsername("test1")).thenReturn(dummy); MvcResult mvcResult = mockMvc.perform(request) .andExpect(status().is2xxSuccessful()) .andReturn(); } public static String asJsonString(final Object obj) { try { return new ObjectMapper().writeValueAsString(obj); } catch (Exception e) { throw new RuntimeException(e); } } }
错误信息
java.lang.AssertionError: Range for response status value 403
expected: but was:<CLIENT_ERROR> Expected :SUCCESSFUL
Actual :CLIENT_ERRORat
org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
at
org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)
at
org.springframework.test.web.servlet.result.StatusResultMatchers.lambda$is2xxSuccessful$3(StatusResultMatchers.java:78)
at
org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:212)
at AuthControllerTest.authenticateUser(AuthControllerTest.java:102)
...(省略栈跟踪剩余部分)
1. 修复初始化方法的语法错误
- 删除
setUp中未定义loginRequest的无效代码,避免初始化失败; - 清理注释残留的语法错误(如多余的
*/); - 提前Mock
jwtUtil.generateJwtCookie的返回值,避免cookies为null:
@BeforeEach public void setUp() { dummy = new UserDetailsImpl(10L,"test1","test1@mail.com","123456",new ArrayList<>()); // 初始化测试用Cookie cookies = ResponseCookie.from("JWT-COOKIE", "dummy-token").path("/").build(); Mockito.when(jwtUtil.generateJwtCookie(dummy)).thenReturn(cookies); }
2. 修正AuthenticationManager的Mock匹配逻辑
测试中实际传入AuthenticationManager的是UsernamePasswordAuthenticationToken实例,原代码用自定义Mock的auth对象匹配,导致调用返回null,触发Spring Security 403拦截。修改为参数匹配器:
// 匹配任意UsernamePasswordAuthenticationToken实例 Authentication auth = Mockito.mock(Authentication.class); Mockito.when(auth.isAuthenticated()).thenReturn(true); // 让认证返回的Principal为测试用户,保证后续Cookie生成逻辑正常 Mockito.when(auth.getPrincipal()).thenReturn(dummy); Mockito.when(authenticationManager.authenticate(Mockito.any(UsernamePasswordAuthenticationToken.class))) .thenReturn(auth);
3. 确保/signin接口允许匿名访问
检查Spring Security配置,确认/api/auth/signin被配置为允许匿名访问。若测试需要临时放行,可添加测试专用的Security配置:
@TestConfiguration static class TestSecurityConfig { @Bean public SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/signin").permitAll() .anyRequest().authenticated() ); return http.build(); } }
4. 完善测试方法的Mock逻辑
调整后的完整测试方法:
@Test @DisplayName("POST /signin") void authenticateUser() throws Exception { LoginRequest authenticationRequest = new LoginRequest("mod", "123456") ; String jsonRequest = asJsonString(authenticationRequest); // Mock认证对象 Authentication auth = Mockito.mock(Authentication.class); Mockito.when(auth.isAuthenticated()).thenReturn(true); Mockito.when(auth.getPrincipal()).thenReturn(dummy); // 正确匹配认证管理器的调用参数 Mockito.when(authenticationManager.authenticate(Mockito.any(UsernamePasswordAuthenticationToken.class))) .thenReturn(auth); // 匹配请求传入的用户名 Mockito.when(userDetailsServiceImpl.loadUserByUsername("mod")).thenReturn(dummy); mockMvc.perform(MockMvcRequestBuilders .post("/api/auth/signin") .content(jsonRequest) .contentType(MediaType.APPLICATION_JSON_VALUE) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(cookie().exists("JWT-COOKIE")) // 验证Cookie返回 .andReturn(); }
5. 测试已登录状态接口的Cookie Mock方式
若需测试受保护接口的Cookie会话,可通过以下两种方式:
- 直接添加Cookie:
mockMvc.perform(MockMvcRequestBuilders.get("/api/protected") .cookie(new Cookie("JWT-COOKIE", "dummy-token"))) .andExpect(status().isOk());
- 使用
@WithMockUser注解快速模拟登录:
@Test @WithMockUser(username = "test1") void testProtectedEndpoint() throws Exception { mockMvc.perform(get("/api/protected")) .andExpect(status().isOk()); }
内容的提问来源于stack exchange,提问作者Hamza Khadhri

