[ bottle-note ] controller test 시 CSRF 403 에러 발생

DeadWhale·2024년 4월 22일
0

bottle-note

목록 보기
1/4

기존 controller layer test 코드가 pr merge 전 403 에러가 발생하는 문제가 발생했습니다.

  • 기존 코드
@DisplayName("유저를 신고 할 수 있다.")  
@Test  
void reportUserTest() throws Exception {  
    // given  
    UserReportRequest request = new UserReportRequest(1L, 2L, UserReportType.OTHER, "신고 내용 쏼라 쏼라 쏼라 ");  
    UserReportResponse response = of(SUCCESS, 1L, 2L, "신고한 유저 이름");  
  
    // when  
    when(userReportService.userReport(request)).thenReturn(response);  
  
    // then  
    mockMvc.perform(post("/api/v1/reports/user")  
          .contentType(MediaType.APPLICATION_JSON)  
          .content(mapper.writeValueAsString(request)))  
       .andExpect(status().isOk())  
       .andExpect(jsonPath("$.success").value("true"))  
       .andExpect(jsonPath("$.code").value("200"))  
       .andExpect(jsonPath("$.data.message").value(SUCCESS.getMessage()))  
       .andExpect(jsonPath("$.data.reportId").value(response.getReportId()))  
       .andExpect(jsonPath("$.data.reportUserId").value(response.getReportUserId()))  
       .andExpect(jsonPath("$.data.reportUserName").value(response.getReportUserName()))  
       .andDo(print());  
}

{org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN=org.springframework.security.web.csrf.DefaultCsrfToken@63a72cc6, SPRING_SECURITY_CONTEXT=SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=user, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_USER]]]}

하지만 매우 친철한 spring CSRF 토큰이 없어서 발생하는 문제라는 것을 설명해주고 있습니다.

원인 유추는 생각보다 간단했습니다.
현재 작업 중인 pr/38에 main을 병합하는 과정하고 추가 된 코드가 이슈가 있을거라 판단이 되었습니다.

다른 브랜치에서 security config 관련 사전 설정이 추가되었는데.
해당 부분의 .csrf(AbstractHttpConfigurer::disable) 가 문제로 파악되었습니다.


CSRF 란?

Cross-Site Request Forgery

  • 공격자가 악의적인 코드를 가진 사이트를 만들어두고 , 로그인된 사용자가 클릭하게 만들어 사용자와 의사와 상관없이 서버로 요청을 발생 시키는 공격
  • 공격에 이용되는 사용자는 로그인된 상태이기 때문에 본인의 의사와 상관 없이, 악의적인 요청을 수행한다.
  • 이런 문제를 방지하기 위해 스프링 시큐리티에서는 CSRF 토큰 을 활용해 토큰 값이 일치하는 경우에만 허용한다.
    - GET 요청에 대해서는 검증하지 않는다.
  • 단, JWT 기반의 무상태 서비스를 구현하는 경우, CSRF 토큰은 필수적이지 않습니다.

해결 방법

해결책은 간단합니다:

// then  
mockMvc.perform(post("/api/v1/reports/user")  
       .contentType(MediaType.APPLICATION_JSON)  
       .with(csrf())  
       .content(mapper.writeValueAsString(request)))  
    .andExpect(status().isOk())  
    .andExpect(jsonPath("$.success").value("true"))  
    .andExpect(jsonPath("$.code").value("200"))  
    .andExpect(jsonPath("$.data.message").value(SUCCESS.getMessage()))  
    .andExpect(jsonPath("$.data.reportId").value(response.getReportId()))  
    .andExpect(jsonPath("$.data.reportUserId").value(response.getReportUserId()))  
    .andExpect(jsonPath("$.data.reportUserName").value(response.getReportUserName()))  
    .andDo(print());

  • mockMvc.perform() 부분에 .with(csrf())) 를 추가하면 된다.

    테스트에서 .with(csrf())를 사용하면 CSRF 토큰이 자동으로 테스트 요청에 포함됩니다.
    이는 실제 웹 애플리케이션에서 폼 제출 시 CSRF 토큰이 포함되는 것을 모방합니다.

    다만 이런 식으로 하면 논리적으로 좀 이상함을 느낄수도 있습니다.
    분명 security에는 csrf을 disable 하였지만. 테스트 시에는 허용하되 통과 하도록 한 것이다보니

좀더 직관적인 방식으로 적용하기로 했습니다.

@WebMvcTest(ReportCommandController.class)  
@WithMockUser 
@Import(SecurityConfig.class)  
class ReportCommandControllerTest {

@Import(SecurityConfig.class)

이 방식은 후속적인 필터나 검증 요소로 인해 충돌이 발생할 가능성이 있으나,
그런 경우 이 글을 통해 추가적인 정보를 제공하는 것이 좋을것 같습니다

0개의 댓글