Spring Security 6.5下JUnit测试OAuth2资源服务器Register API遇401问题
问题
使用JUnit(Spring Security 6.5)测试Register API时遇到异常:API在Postman中正常运行,但JUnit测试返回401而非预期的200。
测试代码
@WebMvcTest(AuthenticationController.class) class AuthenticationControllerTest { @Autowired private MockMvc mockMvc; // ... 其他Mock及依赖 @Test void testRegister() throws Exception { String username = "example"; String password = "exPw123"; String email = "example@gmail.com"; AuthenticationRequest request = new AuthenticationRequest(username, password, email); UserDto userDto = new UserDto(username, email); User user = Mockito.mock(User.class); Mockito.when(user.getDto()).thenReturn(userDto); Mockito.when(authenticationService.register(any(), any(), any(), any())).thenReturn(user); mockMvc.perform(post("/api/auth/v1/register") .with(csrf()) .header("role", "learner") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) .andExpect(jsonPath("$.email").value(email)) .andExpect(jsonPath("$.username").value(username)); } }
SecurityFilterChain配置
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .securityMatcher("/**") .authorizeHttpRequests(auth -> { auth.requestMatchers("/api/auth/**", "/swagger-ui/*", "/v3/api-docs*/**", "/swagger-resources/*", "/swagger-ui.html", "/").permitAll(); auth.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll(); auth.requestMatchers("/learner/**").hasAnyRole("LEARNER", "ADMIN"); auth.requestMatchers("/admin/**").hasAnyRole("ADMIN"); auth.anyRequest().authenticated(); }) .oauth2ResourceServer( oauth2 -> oauth2.jwt( jwtConfigurer -> jwtConfigurer .jwtAuthenticationConverter(jwtAuthenticationConverter()))) .addFilterBefore(jwtCookieFilter, UsernamePasswordAuthenticationFilter.class) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .headers(headers -> headers.frameOptions(frameOptions -> frameOptions.disable())); return http.build(); }
问题细节
- 给测试类添加
@AutoConfigureMockMvc(addFilters = false)可通过测试,但不符合真实场景要求 - 明明配置了
/api/auth/**为permitAll,测试仍返回401 - 尝试添加
@WithMockUser或模拟JWT后,状态码变为200,但响应体为空
测试日志
401情况
MockHttpServletResponse: Status = 401 Error message = Unauthorized Headers = [WWW-Authenticate:"Basic realm="Realm"", X-Content-Type-Options:"nosniff", X-XSS-Protection:"0", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"] Content type = null Body = Forwarded URL = null Redirected URL = null Cookies = []
添加@WithMockUser/@WithMockJWT后的200情况
MockHttpServletResponse: Status = 200 Error message = null Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"0", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"] Content type = null Body = Forwarded URL = null Redirected URL = null Cookies = []
分析与解决
1. 401错误的根源
从401日志里的WWW-Authenticate:"Basic realm="Realm""能看出,测试触发了默认Basic认证,而非你配置的OAuth2 Resource Server逻辑。原因是@WebMvcTest默认只加载控制器和Spring MVC核心组件,你的SecurityFilterChain、jwtCookieFilter等自定义安全Bean没被加载到测试上下文。
Spring Security在找不到自定义SecurityFilterChain时,会启用默认安全规则——要求所有请求走Basic认证,这就是配置了permitAll仍返回401的原因。
解决方法
给测试类添加@Import注解,导入你的安全配置类(即包含securityFilterChain方法的类),让测试上下文加载自定义安全规则:
@WebMvcTest(AuthenticationController.class) @Import(SecurityConfig.class) // 替换为你的安全配置类实际名称 class AuthenticationControllerTest { // ... 原有代码 }
如果jwtCookieFilter有依赖Bean(比如JWT解析工具),用@MockBean模拟这些依赖,避免测试时因缺少Bean报错。
2. 添加@WithMockUser后响应体为空
这是因为你模拟的authenticationService.register返回的是Mockito mock的User对象,控制器序列化UserDto时出现了问题:要么UserDto没有可被Jackson访问的属性,要么mock逻辑没有正确返回可序列化的UserDto实例。
解决方法
- 方法一:返回真实对象替代mock
直接让register方法返回真实的User实例,确保其getDto()方法返回可序列化的UserDto:UserDto userDto = new UserDto(username, email); // 假设User类有对应的构造方法和正确的getDto实现 Mockito.when(authenticationService.register(any(), any(), any(), any())).thenReturn(new User(username, password, email)); - 方法二:确保mock的UserDto可被序列化
检查UserDto类是否有public getter方法,或添加Lombok的@Data注解,保证Jackson能正确读取属性:// UserDto类示例 @Data public class UserDto { private String username; private String email; public UserDto(String username, String email) { this.username = username; this.email = email; } }
3. 额外优化点
- 测试里的
.with(csrf())可以删除,因为你的安全配置已经禁用了CSRF(csrf -> csrf.disable()),添加后属于冗余操作。 - 精准匹配
register方法的参数:比如when(authenticationService.register(eq(username), eq(password), eq(email), eq("learner"))),避免因参数不匹配导致mock逻辑不生效。
内容的提问来源于stack exchange,提问作者phuc luu
相关产品推荐
相关产品推荐

