You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.08 08:39:04