如何Mock SpringSecurity与OncePerRequestFilter(JWT认证过滤器)获取@AuthenticationPrincipal?
我之前在做Spring Boot 2.0项目的时候,也碰到过类似的Mock Spring Security和JWT过滤器的问题,折腾了好一阵子才整理出几套可行的方案,结合你的场景给你详细拆解下:
整体思路
因为你用了@SpringBootTest启动完整的Web上下文,所以核心思路要么是Mock掉JWT过滤器的校验逻辑,要么是直接模拟Security上下文让@AuthenticationPrincipal能拿到用户,或者两种方式结合,根据你的测试需求来选。
方案1:Mock JWT过滤器,手动注入认证用户
这个方案适合不需要测试过滤器本身逻辑,只需要验证控制器业务的场景:
- 首先在测试类里用
@MockBean替换掉你的JwtFilter,这样它不会执行实际的令牌校验; - 在测试前置方法里手动构造认证对象,塞到Security上下文中,让
@AuthenticationPrincipal能获取到。
代码示例:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NfaBackendApplication.class) @AutoConfigureMockMvc public class YourControllerTest { @Autowired private MockMvc mockMvc; // 用Mock替换掉实际的JWT过滤器,跳过令牌校验 @MockBean private JwtFilter jwtFilter; @BeforeEach void setUp() { // 构造模拟的用户对象(替换成你项目里的UserDetails实现类) UserDetails mockUser = User.withUsername("testUser") .password("dummyPassword") .roles("USER") .build(); // 创建认证对象,把模拟用户作为principal Authentication authentication = new UsernamePasswordAuthenticationToken( mockUser, null, mockUser.getAuthorities()); // 设置到Security上下文 SecurityContextHolder.getContext().setAuthentication(authentication); } @AfterEach void tearDown() { // 清理上下文,避免测试间互相影响 SecurityContextHolder.clearContext(); } // 测试示例:验证接口能正确获取到用户 @Test void testApiWithAuthenticationPrincipal() throws Exception { mockMvc.perform(get("/your/api/endpoint")) .andExpect(status().isOk()) .andExpect(content().string(containsString("testUser"))); } }
方案2:用Spring Security测试注解模拟自定义用户
如果你的@AuthenticationPrincipal绑定的是自定义用户对象(不是默认的UserDetails),这个方案更贴合Spring Security的测试生态:
- 先定义一个自定义的测试注解,用来标记需要模拟的用户;
- 实现对应的Security上下文工厂,构造你的自定义用户对象;
- 在测试方法上直接用这个注解,自动注入模拟用户。
代码示例:
首先创建自定义注解:
@Retention(RetentionPolicy.RUNTIME) @WithSecurityContext(factory = WithCustomUserSecurityContextFactory.class) public @interface WithCustomUser { String username() default "testUser"; String[] roles() default {"USER"}; }
然后实现上下文工厂:
public class WithCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithCustomUser> { @Override public SecurityContext createSecurityContext(WithCustomUser annotation) { SecurityContext context = SecurityContextHolder.createEmptyContext(); // 构造你的自定义用户对象(替换成项目里的实际类) CustomUser customUser = new CustomUser(); customUser.setUsername(annotation.username()); customUser.setRoleList(Arrays.asList(annotation.roles())); // 创建认证对象,把自定义用户作为principal Authentication auth = new UsernamePasswordAuthenticationToken( customUser, null, AuthorityUtils.createAuthorityList(annotation.roles())); context.setAuthentication(auth); return context; } }
最后在测试类里使用:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NfaBackendApplication.class) @AutoConfigureMockMvc public class YourControllerTest { @Autowired private MockMvc mockMvc; // 不需要Mock过滤器,注解自动帮我们设置Security上下文 @Test @WithCustomUser(username = "customTestUser", roles = {"ADMIN"}) void testApiWithCustomUser() throws Exception { mockMvc.perform(get("/your/api/endpoint")) .andExpect(status().isOk()) .andExpect(content().string(containsString("customTestUser"))); } }
方案3:生成有效JWT令牌,真实测试过滤器逻辑
如果需要验证JWT过滤器的实际校验逻辑(比如令牌解析、过期校验等),可以直接生成一个有效的令牌,请求时携带:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NfaBackendApplication.class) @AutoConfigureMockMvc public class YourControllerTest { @Autowired private MockMvc mockMvc; // 注入项目里的JWT工具类,用来生成有效令牌 @Autowired private JwtTokenUtil jwtTokenUtil; @Test void testApiWithValidJwtToken() throws Exception { // 生成测试用的有效令牌 String validToken = jwtTokenUtil.generateToken("testUser"); // 请求时携带Authorization头 mockMvc.perform(get("/your/api/endpoint") .header("Authorization", "Bearer " + validToken)) .andExpect(status().isOk()); } }
关键注意事项
- 用
@MockBean替换过滤器时,要确保它不会影响其他依赖该过滤器的Bean,如果有依赖问题,可以考虑在测试配置里临时禁用过滤器; - Spring Boot 2.0中Security上下文是线程绑定的,记得在
@AfterEach里调用SecurityContextHolder.clearContext()清理,避免测试间数据污染; @AuthenticationPrincipal默认会取Authentication对象的principal属性,所以只要你设置的认证对象的principal是对应的用户类型,就能自动注入。
内容的提问来源于stack exchange,提问作者JDev




