📝 서론
JWT 기반 인증을 적용한 Spring Boot 프로젝트에서, 사용자 인증 관련 단위 테스트를 작성하던 중 예상치 못한 오류를 만났다.
java.lang.AssertionError: Status expected:<200> but was:<401>
정상 로그인 후 사용자 정보를 조회하는 API에서 200 OK를 기대했지만, 실제로는 401 Unauthorized가 반환되었다.
MockMvc를 통한 요청이 분명 Authorization 헤더에 JWT를 포함하고 있었음에도, 필터를 거치지 않아 인증에 실패한 것이었다.
🧩 문제 상황 요약
// 테스트 코드 일부
mockMvc.perform(get("/api/users/me")
.header("Authorization", "Bearer " + token)
.with(csrf()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.userId")
.value(1L));
문제는 다음과 같았다.
- 테스트에서 JWT를 발급해 Authorization 헤더에 포함했음
- 하지만 JwtAuthenticationFilter가 실행되지 않아, SecurityContext에 인증 정보가 등록되지 않음
- 그 결과 컨트롤러는 인증되지 않은 요청으로 인식하고 401 응답 반환
🔍 문제 원인 분석
❗ Spring Boot 통합 테스트에서 JwtAuthenticationFilter가 동작하지 않은 이유
- JwtAuthenticationFilter는 커스텀 필터로, Spring Security의 필터 체인에 명시적으로 등록되어야 함
- 테스트에서는 @TestConfiguration을 사용해 필터를 등록했지만, 내부에서 사용하는 의존성(JwtTokenProvider, UserRepository)이 MockBean이기 때문에 null로 주입될 위험이 있었음
- 결과적으로 필터가 정상적으로 등록되지 않거나 작동하지 않아, SecurityContext에 인증 정보가 들어가지 않았던 것
✅ 해결 방법
✅ 1. 필터의 의존성을 안전하게 주입
@TestConfiguration
static class TestSecurityConfig {
@Autowired
JwtTokenProvider jwtTokenProvider;
@Autowired
UserRepository userRepository;
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter(jwtTokenProvider, userRepository);
}
@Bean
public SecurityFilterChain testFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtFilter) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}
@Bean으로 등록할 때 필터 내부에서 사용하는 의존성을 명확히 주입해줘야 필터가 정상 작동한다.
✅ 2. JwtTokenProvider와 Repository에 대한 Mock 설정 추가
given(jwtTokenProvider.validateToken(anyString())).willReturn(true);
given(jwtTokenProvider.getUserId(anyString())).willReturn(1L);
given(userRepository.findById(1L)).willReturn(Optional.of(mockUser()));
✅ 3. 로그로 필터 작동 여부 확인
log.info("🔥 JwtAuthenticationFilter 작동 중 - URI: {}", request.getRequestURI());
→ 테스트 실행 시 로그가 출력되면 필터가 실제로 작동하고 있다는 뜻이다.
✅ 4. 테스트 내 사용자 인증 흐름 확인
mockMvc.perform(get("/api/users/me")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
.with(csrf()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.userId").value(1L))
.andExpect(jsonPath("$.email").value("user@example.com"))
.andExpect(jsonPath("$.nickname").value("dohyunnn"))
.andDo(print());
📌 마무리
| 체크리스트 | 항목 설명 |
| JwtAuthenticationFilter가 동작하는가? | 로그 출력으로 확인 or SecurityFilterChain 직접 구성 |
| 의존성 주입이 제대로 되었는가? | 필터 생성 시 사용하는 빈들을 @Autowired로 주입 |
| JwtTokenProvider의 메서드들이 mock 되었는가? | validateToken, getUserId 등 필수 설정 |
| UserRepository가 유저를 반환하고 있는가? | findById()가 Optional.of(mockUser()) 반환 |