본문 바로가기
포스코x코딩온

[포스코x코딩온] 풀스택 부트캠프 4차 프로젝트 -7

by 김선지 2024. 3. 22.

메소드 뿐 아니라 필터에서 걸려서 예외처리가 되는 경우에는 내가 만든 예외처리가 적용되지 않고 default response인 403에러에 body에 아무것도 주지 않았다. 물론 이렇게 보내면서도 충분히 할 수 있지만 프론트 분들이 불편할 것 같아서 필터도 에러 리스폰스를 넣는 게 좋겠다는 생각이 들었다.

 

 그래서 구글링 해서 찾아보니까 필터단에서는 따로 처리를 해줘야 하고, response 객체에 setStatus()와 getWriter().write() 메소드로 필터 단에서 body를 응답할 수 있다는 블로그를 보았다.

 

https://blog.naver.com/PostView.naver?blogId=qjawnswkd&logNo=222303565093&parentCategoryNo=&categoryNo=&viewDate=&isShowPopularPosts=false&from=postView

 

Jwt 예외처리(MalformedJwtException, ExpiredJwtException, UnsupportedJwtException, SignatureException)

Spring Security 필터에서의 예외는 @ExceptionHandler로 잡히지 않는다. (필터 단이 아닌 BadCreden...

blog.naver.com

 

근데 해도 안된다...

그래서 한가지 든 생각이 저기서 exception이 발생했을 때 status와 response를 모두 set 받긴 하지만 뒤에 필터로 갈 때 덧씌워지는 것이 아닌가. 하는 생각이었다.

그래서 catch문에서 다시 한번 doFilter를 실행했는데 write 메소드가 2번 호출 되었기 때문에 되지 않는다는 exception이 발생했다. 조금만 더 파면 될 것 같았다.

 

그렇게 학원에서 방법을 찾다가 난 항상 집에서 답을 찾기 때문에 집에가서 하기로 했다.

 

집에와서 해결책을 찾다가 controller 단에서 throw된 예외들에 대한 response를 한번에 해줄 수 있는 클래스가 있다는 사실을 알게 되서 적용시켰다.

 

package com.weatherable.weatherable.utils;

import com.weatherable.weatherable.enums.DefaultRes;
import com.weatherable.weatherable.enums.StatusCode;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<DefaultRes<String>> handleException(
            Exception e
    ){
        return new ResponseEntity<>(
                DefaultRes.res(StatusCode.UNAUTHORIZED, e.getMessage()),
                HttpStatus.UNAUTHORIZED
        );
    }

}

 

이렇게 ControllerAdvice와 ExceptionHandler라는 annotation을 붙이면 contoller 단에서 나온 에러는 모두 이 핸들러가 처리하게 되어 답변을 해준다.

난 지금까지 하나하나 try catch에 묶어서 해줬는데.... 좀 더 빨리 볼걸 그랬다.

다만 try catch에 묶어서 처리했기 때문에 바로 컨트롤러에서 파라미터를 받아오는 곳에서 발생하는 에러는 커버할 수 없었다.

이렇게 하니까 exceptionHandler가 그 부분까지 커버를 해주는 것 같다. 다행이다.

 

 

계속 내가 만든 커스텀 필터에서 response를 해줄 방법을 찾다가 위 분의 다른 포스팅도 발견했다.

 

https://blog.naver.com/PostView.nhn?blogId=qjawnswkd&logNo=222303477758

 

Spring Security 인증 인가 예외 처리(AuthenticationEntryPoint, AccessDeniedHandler)

Spring Security는 인증 인가 실패시 FilterSecurityInterceptor가 2가지의 예외를 발생시킨다 1. 인...

blog.naver.com

 

이건 위에 ControllerAdvice와 같이 진짜 exception에서 default로 해줄 수 있는 설정인 것 같다. 지금까지 이 설정 자체가 response body를 반환하지 않는 것이 default 였기 때문에 아무것도 status 코드만 반환해주지 않았을까 싶다.

그래서 설정하고 filterChain에 추가해주니까 에러 응답을 해주기는 한다. 다만 에러 설명이 항상 동일하다.

jwt 인증을 위한 조건이 만족되지 않았습니다.?? 이런 느낌의 말이었다.

 

그래서 첫번째 포스팅의 아이디어를 조금 바꿔서 다시 한번 시도 해봤더니 바로 성공했다.

 

검증은 수명을 10초로 설정하고 postman의 Authorization 토큰을 바꾸어도 보고, 만료시켜도 보고 하면서 검증했다.

근데 조금 이상하다.

default로 반환해주는 것과 이렇게 catch문으로 response를 해주는 건 별개의 상황이라고 생각하고 실제로도 별개인 것 같은데 왜 둘 다 적용했을 때만 성공하는 걸까??

 

그래서 default 핸들러를 전부 주석처리하고 해봤다.

근데 된다..???? 내가 이거 때문에 5시간을 삽질했는데..

노트북이 이상하거나 그때 내가 무언가 잘못 적었던 것 같다. 뭐 아무튼 되니까 된거지. 좋은 게 좋은거다.

 


Repository 단에서 test도 해봐야겠다고 생각했다.

test를 하다보니까 신기한게 있었다.

 

@Test
@Transactional
void likeClosetTest() {
    closetRepository.likeCloset(3L);
    var closetEntity = closetRepository.getByIdAndActive(3L, true);
    assertTrue(closetEntity.get().isLiked());
}

다음 코드를 실행하면 like가 true가 되어있어서 test가 성공하는데, 막상 mysqlWorkBench에 가보니까 false로 되어있었다.

음... 왜이러지..? 분명 통과하는데?

이건 GPT가 잘대답해주지.

그렇다고 한다.

이 답변을 토대로 추론하자면

@Test 어노테이션과 같이 있으면 실행하고 바로 롤백하는데

@Service 계층에서 실행될 때 @Transactional이 있다면 예외가 발생하지 않는다면 변경 사항을 저장하고 만약에 service 메소드가 끝나기 전에 Exception이 발생하면 이금까지 가해졌던 모든 데이터베이스의 변경사항을 롤백해준다는 것 같다.

그렇다면 retrieve 요청 말고 모든 post, update, put, delete 요청의 경우 변경이 이루어지는 것이기 때문에 @Transactional을 붙여야 한다는 것이 된다.

음... 진짜 이 기능은 Spring boot가 제공해주는 기능들 중 단연 획기적인 것 같다.

 

다시 캐물으니까 이런 답변을 줬다.