如何使@WebMvcTest适配OAuth2(JWT)完成控制器单元测试?
我完全懂你在做基于JWT的OAuth2控制器单元测试时遇到的挫败感!Spring官方文档在这块的指导确实太有限了——不管是用@WithMockUser、@WithUserDetails,甚至自己写自定义@WithSecurityContext注解+实现UserSecurityContextFactory,到了安全表达式(比如@PreAuthorize里的逻辑)评估环节,SecurityContext里始终是匿名用户,根本达不到模拟认证用户的效果。
为什么常规注解不管用?
核心原因是:基于JWT的OAuth2认证流程,是靠解析请求头里的Token来生成JwtAuthenticationToken的,而@WithMockUser这类注解是直接往SecurityContext里塞普通的UsernamePasswordAuthenticationToken,和OAuth2的认证上下文体系不匹配。安全表达式里的逻辑(比如获取用户权限、租户信息等)依赖的是OAuth2特有的认证对象,自然识别不到你模拟的用户。
我找到的临时解决方案:Mock Token解析组件
我的思路是直接Mock掉OAuth2流程里负责解析Token并生成认证对象的核心组件,让它在测试环境下直接返回我们预设的JwtAuthenticationToken。具体步骤大概是这样:
- 在测试类中,用
@MockBean替换项目中原有的Token解析服务(比如JwtDecoder或者自定义的TokenService) - 用Mockito预设这个Mock Bean的行为,让它在接收到任意有效Token(或者我们指定的测试Token)时,返回包含预设用户信息、权限的
JwtAuthenticationToken - 配置MockMvc时,确保OAuth2的过滤器链被正确加载,这样请求就能通过Mock的Token服务完成认证
举个简单的代码示例:
@WebMvcTest(YourController.class) class YourControllerTest { @Autowired private MockMvc mockMvc; @MockBean private JwtDecoder jwtDecoder; @BeforeEach void setUp() throws Exception { // 构建预设的JWT信息 Jwt jwt = Jwt.withTokenValue("test-token") .header("alg", "HS256") .claim("sub", "test-user") .claim("authorities", List.of("ROLE_ADMIN", "ROLE_USER")) .build(); // 预设JwtDecoder的行为 when(jwtDecoder.decode(anyString())).thenReturn(jwt); } @Test void testSecuredEndpoint() throws Exception { mockMvc.perform(get("/api/secured") .header(HttpHeaders.AUTHORIZATION, "Bearer test-token")) .andExpect(status().isOk()); } }
不过说实话,我也不确定这种Mock Token解析组件的方式是不是最简洁高效的——毕竟每次都要手动构建JWT和认证对象,有点繁琐。如果大家有更优雅的方案(比如自定义适配OAuth2的测试注解、或者用Spring Security提供的更便捷的测试工具),欢迎分享交流!
内容的提问来源于stack exchange,提问作者ch4mp

