TEST/JUnit

[Mockito] @WebMvcTest 401/403 에러 처리하기

블로그 주인장 2023. 12. 20.

@WebMvcTest 401/403 에러 처리하기

API 개발 후 테스트를 하는 도중에 발생한 에러를 처리하는 방법에 대해 알아보겠습니다.


 

테스트 코드

@WebMvcTest(AuthController.class)
class AuthControllerTest {

  @MockBean
  private AuthService authService;

  @MockBean
  private MailService mailService;

  @MockBean
  private TokenProvider tokenProvider;

  @Autowired
  private MockMvc mockMvc;

  @Autowired
  private ObjectMapper objectMapper;

  @Test
  @DisplayName("회원가입 성공")
  void success_signUp() throws Exception {
    //given
    SignUpDto user = SignUpDto.builder()
        .email("test@test.com")
        .password("12345678")
        .nickname("김철수")
        .phoneNumber("010-1234-5678")
        .build();
    given(authService.signUp(any())).willReturn(user);

    //when
    //then
    mockMvc.perform(post("/api/auth/signUp")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(user))
        .andExpect(status().isCreated())
        .andExpect(jsonPath("$.email").value("test@test.com"))
        .andExpect(jsonPath("$.password").value("12345678"))
        .andDo(print());
  }

 

컨트롤러 코드

@PostMapping("/signUp")
public ResponseEntity<?> signUpMember(@RequestBody SignUpDto request) {
  return ResponseEntity.status(HttpStatus.CREATED).body(memberService.signUp(request));
}

 

 

 

테스트 시에 발생한 에러 (403)


에러 원인

- 스프링 시큐리티의 CSRF 로 인한 에러

 

 

에러 이유 및 해결방법

 

# CSRF(Cross Site Request Forgery)

- 공격자가 악의적인 코드를 심어놓은 사이트를 만들고, 로그인 된 사용자가 클릭하게 만들어서 사용자의 의지와 무관한 요청을 발생시키는 공격

- 사용자는 로그인한 상태이고 쿠키, 권한을 갖고 있기 때문에 공격자가 위조한 웹사이트에 방문하게 되면 사용자가 모르게 악의적인 POST, DELETE 요청을 정상 수행하도록 만들어버리는 공격

- 이로 인해 스프링 시큐리티에서는 "CSRF 토큰" 을 이용해 토큰 값을 비교해서 일하는 경우에만 메서드를 만들도록 처리한다. (Synchronizer Token Pattern)

- 즉, 스프링 시큐리티가 적용되면서 CSRF 공격에 대한 방어가 활성화 된 것이고, 이로 인해 IDE에서 진행한 테스트 과정을 다른 사이트의 요청으로 인지한 것이다.

 

# Synchronizer Token Pattern

 - 서버가 뷰를 만들어줄 때 사용자 별로 랜덤값을 만들어 세션에 저장한 다음 이를 뷰 페이지에 같이 담아 넘겨주게 된다.

 - 클라이언트는 HTTP 요청마다 숨겨진 csrf 토큰을 같이 넘겨줘야하는 방식이다.

 - 서버는 HTTP Request에 있는 csrf 토큰값과 세션에 저장되어 있는 토큰값을 비교해 일치하는 경우에만 처리를 진행한다

 - 위조된 사이트의 경우 csrf 토큰값이 일치하지 않기 때문에 공격자가 악의적인 코드를 심어놔도 이를 실행하지 않는다.

 * GET 요청에 대해서는 csrf 검증을 수행하지 않는다.

 

 

# with(csrf())를 추가한 경우

 - 파라미터로 _csrf 값을 보내주는 것을 확인할 수 있다.

 

# with(csrf())를 사용하지 않은 경우

 - 세션에 저장된 CSRF 값과 매칭되지 않음

 - 요청을 수행하지 못하고 403 에러 반환

 

 

# 프로젝트에서 csrf 기능을 disable 한 이유?

 - csrf 토큰 방식은 각 사용자에 대한 세션을 이용하는 방식이다.

 - 웹 브라우저를 통한 접근의 경우, 세션/쿠키를 사용해 상태를 유지하려고 하는 경우 csrf를 사용하는 것이 안전한다.

 - REST API의 경우 보통 무상태성을 유지하며 JWT와 같은 코근 방식으로 인증하게 되면 요청이 세션에 의존하지 않기 때문이다.

 * Thymeleat와 같은 템플릿 엔진을 통해 View를 제공하는 웹/앱 어플리케이션의 경우 -> csrf 사용 권장

 * REST API 제공하는 애플리케이션 -> csrf 사용하지 않아도 무방

 

 

테스트 시에 발생한 에러 (401)


에러 원인

-  API 권한 미설정

 

 

에러 이유 및 해결방법

# @SpringBootTest vs @WebMvcTest 

- SpringBootTest의 경우 어노테이션(@Controller, @Service @Bean) 등 전부 불러와서 실제 환경과 동일하게 테스트 할 때 사용한다.

- @WebMvcTest의 경우에는 절반 가량의 스프링 빈만 불러와서 사용한다.

- 대신에, @MockBean으로 주입해줄 경우 스프링 빈으로 저장할 수 있다.

 

# SpringBootWebSecurityConfiguration

 - 스프링 시큐리티는 맨 처음 적용하면 페이지에 들어가면 바로 로그인 창이 뜨게 되어있다.

 - 모든 요청에 대해 아무 권한이나 갖고있으면 되도록 설정되어 있다.

 - 테스트를 위해 매핑 신호를 날리면 스프링 시큐리티로 인해 401 Unauthorized 에러를 반환한다.

 - 모든 요청에 대해 권한이 필요하도록 기본적으로 설정되어 있기 때문이다.

 

# 설정 방법

 

 1. 어노테이션을 활용하여 권한을 넘겨주는 방법

  • @WithMockUser
  • @WithAnonymousUser - 미인증 사용자
  • @WIthUserDetails - 메서드가 principal 내부의 값을 직접 사용하는 경우(별도의 사전 설정이 필요하다)

 

 

 2. MockMvcBuilder이용하여 MockMvc 초기화

  • MockMvc를 @Autowired 를 하지 않고 테스트 초기에 초기화를 하는 작업이다.
  • @BeforeEach 어노테이션을 이용해서 테스트 메서드가 실행되기 전에 수행되는 어노테이션으로 테스트를 진행할 때마다 mockMvcBuilder를 이용해서 Mock에 독립적인 클래스(controller)를 선언하고 인스턴스를 생성한다.

 

 

참고


https://kang-james.tistory.com/entry/%EB%B3%B4%EC%95%88%EC%9D%B8%EC%A6%9D-CSRF-%EB%A1%9C-%EC%9D%B8%ED%95%B4%EC%84%9C-403%EC%97%90%EB%9F%AC%EA%B0%80-%EB%B0%9C%EC%83%9D%ED%96%88%EC%9D%84-%EB%95%8C

https://iseunghan.tistory.com/302

https://velog.io/@wellsy1012/SpringMockito-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%8B%9C-403-Error-%EC%82%BD%EC%A7%88

https://sedangdang.tistory.com/303

https://velog.io/@wellsy1012/SpringMockito-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%8B%9C-403-Error-%EC%82%BD%EC%A7%88

https://adjh54.tistory.com/347#1.%20Mock%20%EC%B4%88%EA%B8%B0%ED%99%94-1

 

반응형

댓글