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

Spring Boot中带@AuthenticationPrincipal的Rest Controller测试问题

问题分析与解决方案

核心问题点

1. 第一个测试失败原因

你在@BeforeEach中设置了SecurityContext,但控制器无法获取到Mock的CustomUser,主要问题有两个:

  • 使用@AutoConfigureMockMvc(addFilters = false)关闭了过滤器,但Spring Security处理@PreAuthorize的方法安全拦截器仍会生效,此时测试环境未正确初始化Spring Security的上下文解析支持,导致@AuthenticationPrincipal无法从SecurityContext中正确提取用户对象。
  • 代码存在拼写错误:private CustomerUser userMock;应为private CustomUser userMock;,这会导致变量引用异常。

2. 自定义注解方案失败原因

  • 类型不匹配:工厂类中Mock的是IUser.class,但控制器参数是CustomUser,Spring无法将IUser实例转换为CustomUser,自然无法注入预期的Mock对象。
  • 工厂类错误标记@Service:该工厂是测试专用组件,不需要@Service注解,反而可能导致测试环境无法正确扫描,应改为@Component或在测试类中手动注册。

正确解决方案

方案一:修复SecurityContext设置,启用Spring Security测试支持

保留@PreAuthorize验证,确保@AuthenticationPrincipal能正确获取Mock对象:

@WebMvcTest(RestController.class)
// 使用@WithMockUser满足isAuthenticated()验证,再替换自定义用户对象
@WithMockUser
class RestControllerTest {

    @Autowired private MockMvc mockMvc;
    private CustomUser userMock;

    @BeforeEach
    public void setupSecurityContext() {
        // 修复拼写错误
        userMock = Mockito.mock(CustomUser.class);
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        // 创建类型匹配的Authentication,主体为CustomUser
        Authentication auth = new PreAuthenticatedAuthenticationToken(userMock, null, Collections.emptyList());
        context.setAuthentication(auth);
        SecurityContextHolder.setContext(context);
    }

    @Test
    void test() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/api/get")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.status().isOk());
    }
}

方案二:修复自定义@WithMockCustomUser注解

修正类型匹配问题,正确注册工厂类:

1. 定义注解与工厂类

@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser {
}

// 替换@Service为@Component,确保测试环境能扫描到
@Component
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {

    @Override
    public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        // Mock正确的CustomUser类型
        CustomUser mockUser = Mockito.mock(CustomUser.class);
        Authentication auth = new PreAuthenticatedAuthenticationToken(mockUser, null, Collections.emptyList());
        context.setAuthentication(auth);
        return context;
    }
}

2. 测试类修改

@WebMvcTest(RestController.class)
// 保留Spring Security测试支持,无需关闭过滤器
class RestControllerTest {

    @Autowired private MockMvc mockMvc;

    @Test
    @WithMockCustomUser // 使用自定义注解注入Mock用户
    void test() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/api/get")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.status().isOk());
    }
}

方案三:完全绕过Security,直接模拟@AuthenticationPrincipal注入

如果需要完全禁用安全功能(包括@PreAuthorize验证),可以自定义ArgumentResolver直接注入Mock对象:

@WebMvcTest(RestController.class)
@AutoConfigureMockMvc(addFilters = false)
// 禁用方法安全验证
@EnableMethodSecurity(prePostEnabled = false)
class RestControllerTest {

    @Autowired private MockMvc mockMvc;

    // 自定义参数解析器,处理@AuthenticationPrincipal注解
    @TestConfiguration
    static class TestConfig {
        @Bean
        public HandlerMethodArgumentResolver authenticationPrincipalResolver(CustomUser mockUser) {
            return new HandlerMethodArgumentResolver() {
                @Override
                public boolean supportsParameter(MethodParameter parameter) {
                    return parameter.hasParameterAnnotation(AuthenticationPrincipal.class)
                            && parameter.getParameterType().equals(CustomUser.class);
                }

                @Override
                public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
                    return mockUser;
                }
            };
        }
    }

    // 将Mock的CustomUser注册为Bean
    @Bean
    public CustomUser mockCustomUser() {
        return Mockito.mock(CustomUser.class);
    }

    @Test
    void test() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/api/get")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.status().isOk());
    }
}

内容的提问来源于stack exchange,提问作者vivi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 05:40:38