본문 바로가기

기타/트러블 슈팅

Spring Security 테스트에서 @WithMockUser와 CSRF 이슈 해결하기

반응형

 

프로젝트에 처음으로 테스트 코드를 도입하면서 @WithMockUser 를 사용한 테스트에서 예상치 못한 문제를 경험했다.

@WithMockUser 로 인증된 사용자로 요청을 목킹하고 있었는데, GET 요청에서는 문제없이 잘 작동하지만 POST 요청에서는 403 Forbidden 이 발생하는 것이다.

 

@Test
@WithMockUser(roles = "SELLER")
@DisplayName("이름 없이 스토어 생성을 요청하면, 400 에러를 반환한다.")
void test1() throws Exception {
    // Given
    CreateStoreRequest request = CreateStoreRequest.builder()
            .description("Test description")
            .build();

    // Expected
    mockMvc.perform(MockMvcRequestBuilders.post("/api/store")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(request))
            )
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.message").value("스토어 이름은 필수 입니다."))
            .andDo(print());
}

 

 

원인 분석

Spring Security 의 CSRF 보호 기능이 문제였다.

Spring Security 는 기본적으로 CSRF 공격을 방지하기 위해 POST, PUT, DELETE 등 요청에는 CSRF 토큰이 필요하다.

사용자를 목킹할때, CSRF 토큰이 포함되지 않았기 때문에 403 Forbidden 오류가 발생하였다.

 

Spring Security 설정에 csrf() 를 disable 시켰지만, 테스트 코드에서는 가볍게 진행하기 위해 SecurityConfig.class를 빈으로 등록하지 않아, 해당 설정이 적용되지 않았다.

 

 

해결 방법

mockMvc.perform(MockMvcRequestBuilders.post("/api/store")
                .with(csrf())  // CSRF 토큰 포함
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request))
        )
        .andExpect(status().isBadRequest())
        .andExpect(jsonPath("$.message").value("스토어 이름은 필수 입니다."))
        .andDo(print());

 

'with(csrf())' 메서드를 사용하여 요청에 csrf 토큰을 포함시켰다. 

 

SecurityConfig.class 를 빈으로 등록시킬까? 고민했지만 그럼 의존관계에 있는 다른 클래스도 빈으로 등록하여야 해서 요청에 csrf 토큰을 추가하기로 했다.

반응형