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
相关产品推荐
相关产品推荐

