Spring Boot WebMvcTest测试JWT接口时注入AuthenticationPrinciple方法
@WebMvcTest 是Spring Boot提供的MVC层切片测试注解,默认仅加载Web层相关组件(控制器、视图解析器、MVC拦截器、Spring Security默认Web安全配置等),不会加载业务层、持久层Bean,也不会自动装配自定义的CustomUserDetailsService等安全相关业务组件。两类报错的核心原因分别是:
- 项目自定义的Spring Security配置依赖了
CustomUserDetailsService,但切片上下文里不存在该Bean,因此抛出NoSuchBeanDefinitionException - 手动传递的AUTH_HEADER未被完整安全过滤链解析(相关认证逻辑依赖的Bean未加载),SecurityContext中无有效认证信息,因此控制器方法中
@AuthenticationPrincipal注入的参数为null
1. 引入Spring Security测试组件
在测试作用域引入spring-security-test依赖,Maven配置如下:
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency>
Gradle配置如下:
testImplementation 'org.springframework.security:spring-security-test'
2. 模拟安全配置依赖的业务Bean
在测试类中通过@MockBean注解注入CustomUserDetailsService的模拟实例,保证Spring Security配置可以正常初始化,不需要实现真实的数据库查询逻辑。如果自定义安全配置还依赖JWT解析、权限校验等其他业务Bean,统一通过@MockBean注入模拟实例即可。
3. 直接构造认证信息绑定到请求上下文
不需要手动拼接Token走完整认证流程,通过spring-security-test提供的工具直接构造认证后请求,即可让@AuthenticationPrincipal正常注入参数,两种常用实现方式:
快速模拟场景:@WithMockUser注解
适合不需要自定义用户主体字段的简单校验场景:
@Test @WithMockUser(username = "testUser", roles = "USER") void test_get() throws Exception { mockMvc.perform(get("/your/api/path")) .andExpect(status().isOk()); }
自定义主体场景:RequestPostProcessor构造自定义UserPrinciple
如果控制器逻辑强依赖自定义UserPrinciple中的业务字段(如用户ID、权限集合等),直接构造测试用的UserPrinciple对象绑定到请求:
@Test void test_get() throws Exception { // 构造业务侧定义的UserPrinciple测试实例 UserPrinciple testUser = new UserPrinciple(); testUser.setId(1L); testUser.setUsername("testUser"); testUser.setRoles(Set.of("ROLE_USER")); mockMvc.perform(get("/your/api/path") .with(user(testUser).roles("USER"))) .andExpect(status().isOk()); }
该写法会直接将构造的testUser对象绑定到控制器方法的@AuthenticationPrincipal参数,不会出现空指针问题。
完整测试类参考
@WebMvcTest(controllers = DemoController.class) // 存在自定义Spring Security配置类时添加@Import导入,使用默认安全配置可省略 @Import(SecurityConfig.class) class DemoControllerTest { @Autowired private MockMvc mockMvc; @MockBean private CustomUserDetailsService customUserDetailsService; // 控制器依赖的其他业务Service同样通过@MockBean注入模拟实例 // @MockBean // private DemoBizService demoBizService; @Test void test_get() throws Exception { UserPrinciple testUser = new UserPrinciple(); testUser.setId(1L); testUser.setUsername("test"); mockMvc.perform(get("/demo") .with(user(testUser))) .andExpect(status().isOk()); } }
补充说明:如果项目使用JWT认证,也可以为测试环境单独编写极简安全配置,直接放开接口认证规则、或者注册测试专用的轻量认证过滤器,不需要加载生产环境的完整JWT解析逻辑,可进一步提升切片测试的执行速度。
内容的提问来源于stack exchange,提问作者Sha

