如何在Spring Boot与Mockito测试中模拟JWT Token完成鉴权接口测试
问题根因
你当前的测试失败主要由以下3个问题导致:
- Mock对象未配置打桩规则:你已经通过
@MockBean注入了JwtUtil、MyUserDetailsService,但没有为这些Mock对象的方法预设返回值,过滤器调用jwtUtil.extractUsername()、userDetailsService.loadUserByUsername()时会返回null,导致鉴权流程中断,请求不会走到控制器层,自然返回空响应体。 JwtRequestFilter存在逻辑漏洞:当Authorization头合法但解析出的用户名为空时,过滤器既没有返回错误响应,也没有调用后续过滤器链,请求被直接挂起,最终返回空内容。- 测试打桩顺序错误:你将
categoryCommandService的打桩逻辑放在了第一次mockMvc.perform()之后,虽然第二次请求用到了打桩规则,但鉴权失败的情况下请求根本到不了服务层。
修复方案
第一步:修复JWT过滤器逻辑
修改JwtRequestFilter的doFilterInternal方法,补全异常分支的响应逻辑:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String path = request.getRequestURI(); if (path.equals(SIGN_UP_URL)) { chain.doFilter(request, response); return; } final String authorizationHeader = request.getHeader("Authorization"); String username = null; String jwt = null; if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { jwt = authorizationHeader.substring(7); username = jwtUtil.extractUsername(jwt); } else { response.setStatus(HttpStatus.FORBIDDEN.value()); return; // 非法请求直接返回,不继续执行 } if (username != null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtUtil.validateToken(jwt, userDetails)) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken .setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } chain.doFilter(request, response); } else { // 用户名解析失败直接返回403 response.setStatus(HttpStatus.FORBIDDEN.value()); return; } }
第二步:调整测试逻辑,补全Mock打桩规则
修改测试方法,先配置所有Mock对象的返回规则,再发起请求:
@Test void testCreateCategory() throws Exception { // 1. 构造测试数据 CategoryCreateDto categoryCreateDto = new CategoryCreateDto("category"); CategoryCreateDto categoryCreateResponseDto = new CategoryCreateDto(UUID.fromString("2da4002a-31c5-4cc7-9b92-cbf0db998c41"), "category"); String jsonCreate = asJsonString(categoryCreateDto); String jsonResponse = asJsonString(categoryCreateResponseDto); // 2. 补全JWT相关Mock对象的打桩规则 UserDetails mockUser = new User("foo@email.com", "password", Collections.emptyList()); when(jwtUtil.extractUsername(anyString())).thenReturn("foo@email.com"); when(myUserDetailsService.loadUserByUsername("foo@email.com")).thenReturn(mockUser); when(jwtUtil.validateToken(anyString(), eq(mockUser))).thenReturn(true); // 3. 配置业务服务的打桩规则 when(categoryCommandService.createCategory(categoryCreateDto)).thenReturn(categoryCreateResponseDto); // 4. 构造请求并执行测试 RequestBuilder request = MockMvcRequestBuilders .post("/api/adverts/category") .content(jsonCreate) .header("Authorization", "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmb29AZW1haWwuY29tIiwiZXhwIjoxNjM4ODU1MzA1LCJpYXQiOjE2Mzg4MTkzMDV9.q4FWV7yVDAs_DREiF524VZ-udnqwV81GEOgdCj6QQAs") .contentType(MediaType.APPLICATION_JSON_VALUE) .accept(MediaType.APPLICATION_JSON); MvcResult mvcResult = mockMvc.perform(request) .andExpect(status().is2xxSuccessful()) .andExpect(content().json(jsonResponse, true)) .andExpect(jsonPath("$.id").value("2da4002a-31c5-4cc7-9b92-cbf0db998c41")) .andExpect(jsonPath("$.title").value("category")) .andReturn(); logger.info(mvcResult.getResponse().getContentAsString()); }
可选优化方案
如果你不想模拟JWT工具类,可以直接使用真实的JwtUtil生成有效token,更贴近实际运行场景:
- 移除
@MockBean private JwtUtil jwtUtil;,直接注入真实的JwtUtil实例 - 测试中直接生成token:
@Autowired private JwtUtil jwtUtil; // 测试方法内生成有效token UserDetails mockUser = new User("foo@email.com", "password", Collections.emptyList()); String validToken = jwtUtil.generateToken(mockUser); // 请求头传 "Bearer " + validToken 即可
内容的提问来源于stack exchange,提问作者Francislainy Campos
相关产品推荐
相关产品推荐

