Java中如何测试带@Secured注解的受保护端点/REST Controller?
不生效核心原因
- 测试环境未加载Spring Security过滤器链:使用MockMvc测试时如果没有加
@AutoConfigureMockMvc注解,安全校验逻辑不会被触发 - 未开启
@Secured注解支持:@Secured属于可选注解,需要全局/测试类上加@EnableMethodSecurity(securedEnabled = true)(Spring Security 5.7+)或@EnableGlobalMethodSecurity(securedEnabled = true)(旧版本)才会生效 - 角色前缀配置错误:
@Secured注解默认匹配带ROLE_前缀的权限,@WithMockUser的roles属性会自动拼接ROLE_前缀,不要手动加前缀重复配置
完整测试实现示例
测试依赖
确保项目构建配置中已引入spring-boot-starter-test和spring-security-test测试依赖。
测试类代码
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; // 若主启动类已开启Secured注解支持,可去掉@EnableMethodSecurity注解 @SpringBootTest @AutoConfigureMockMvc @EnableMethodSecurity(securedEnabled = true) public class ViewerEditorEndpointTest { @Autowired private MockMvc mockMvc; @MockBean private ViewerEditorService viewerEditorService; // 测试有权限的用户调用编辑接口 @Test @WithMockUser(username = "test01", roles = {"VOLUNTEER"}) void editAccountInfo_withValidRole_return200() throws Exception { // 构造mock返回 UserDTO mockReturn = new UserDTO(); mockReturn.setUsername("test01"); mockReturn.setNickname("测试用户"); when(viewerEditorService.editAccountInfo(eq("test01"), any(UserDTO.class))) .thenReturn(Optional.of(mockReturn)); // 执行请求并校验结果 mockMvc.perform(put("/account/test01") .contentType(MediaType.APPLICATION_JSON) .content("{\"username\":\"test01\",\"nickname\":\"测试用户\"}")) .andExpect(status().isOk()) .andExpect(jsonPath("$.nickname").value("测试用户")); } // 测试无权限的用户调用编辑接口 @Test @WithMockUser(username = "test01", roles = {"UNKNOWN"}) void editAccountInfo_withInvalidRole_return403() throws Exception { mockMvc.perform(put("/account/test01") .contentType(MediaType.APPLICATION_JSON) .content("{}")) .andExpect(status().isForbidden()); } // 测试查询接口,当前登录用户与查询用户名一致 @Test @WithMockUser(username = "test01") void getAccountInfo_sameUsername_returnUserInfo() throws Exception { UserDTO mockReturn = new UserDTO(); mockReturn.setUsername("test01"); when(viewerEditorService.getAccountInfo("test01")) .thenReturn(Optional.of(mockReturn)); mockMvc.perform(get("/account/test01")) .andExpect(status().isOk()) .andExpect(jsonPath("$.username").value("test01")); } // 测试查询接口,当前登录用户与查询用户名不一致 @Test @WithMockUser(username = "test01") void getAccountInfo_differentUsername_returnNull() throws Exception { mockMvc.perform(get("/account/test02")) .andExpect(status().isOk()) .andExpect(content().string("null")); } }
额外注意事项
@WithUserDetails注解需要测试环境存在可用的UserDetailsService实现,且对应用户账号在用户库中存在,仅需要真实用户上下文时使用,普通场景用@WithMockUser足够- 如果需要自定义角色前缀规则,可通过
GrantedAuthorityDefaultsBean修改全局前缀配置 - 不同测试方法的安全上下文独立,不会互相影响
内容的提问来源于stack exchange,提问作者shenada
相关产品推荐
相关产品推荐

