测试安全配置的痛点



安全配置经常涉及多个组件(Filter、AuthenticationManager、UserDetailsService)的协作,手动测试需要启动整个 Web 容器,非常耗时。Spring Security 提供了 MockMvc 支持,让安全测试变得简单。
集成测试基础
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @SpringBootTest @AutoConfigureMockMvc class SecurityTest {
@Autowired private MockMvc mockMvc;
@Test void unprotectedEndpoint_returns200() throws Exception { mockMvc.perform(get("/public/hello")) .andExpect(status().isOk()); }
@Test void protectedEndpoint_withoutAuth_returns401() throws Exception { mockMvc.perform(get("/api/admin")) .andExpect(status().isUnauthorized()); }
@Test void protectedEndpoint_withAuth_returns200() throws Exception { mockMvc.perform(get("/api/admin") .with(username("admin").password("password"))) .andExpect(status().isOk()); } }
|
使用 WithMockUser 简化测试
WithMockUser 是最常用的测试注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Test @WithMockUser(username = "user1", roles = {"USER"}) void userCanAccessUserEndpoint() throws Exception { mockMvc.perform(get("/api/user")) .andExpect(status().isOk()); }
@Test @WithMockUser(username = "admin", roles = {"ADMIN"}) void adminCanAccessAdminEndpoint() throws Exception { mockMvc.perform(get("/api/admin")) .andExpect(status().isOk()); }
@Test @WithMockUser(roles = {"USER"}) void userCannotAccessAdminEndpoint() throws Exception { mockMvc.perform(get("/api/admin")) .andExpect(status().isForbidden()); }
|
使用 WithUserDetails 加载真实用户
当你需要测试真实的用户数据(来自数据库或 LDAP)时,使用 WithUserDetails:
1 2 3 4 5 6
| @Test @WithUserDetails("admin") void testWithRealUser() throws Exception { mockMvc.perform(get("/api/user/profile")) .andExpect(jsonPath("$.username").value("admin")); }
|
方法级安全的测试
1 2 3 4 5 6 7 8 9 10 11 12
| @Test @WithMockUser(roles = {"ADMIN"}) void adminCanDeleteUser() { userService.deleteUser(1L); }
@Test @WithMockUser(roles = {"USER"}) void userCannotDeleteUser() { assertThrows(AccessDeniedException.class, () -> userService.deleteUser(1L)); }
|
测试 SecurityContext
1 2 3 4 5 6 7 8
| @Test void testSecurityContext() { SecurityTestUtils.authenticatedWith("admin", "ROLE_ADMIN"); Authentication auth = SecurityContextHolder.getContext().getAuthentication(); assertTrue(auth.isAuthenticated()); assertEquals("admin", auth.getName()); }
|
测试配置的最佳实践
- 为每个安全场景编写独立的测试方法
- 使用
@WithMockUser 覆盖不同的角色组合
- 测试认证成功、失败、权限不足三种情况
- 为方法级安全编写单元测试,为 URL 级安全编写集成测试