Filter에서 예외처리

ys·2024년 3월 24일

SpringSecurity+JWT

목록 보기
2/3
  • JwtUtil에서 validToken() 메서드를 이용해서, 해당 예외가 발생하면
  • 우리가 만들어둔 예외를 던져서 예외처리를 하려고 한다

JwtUtil의 validToken()

//  parser을 이용해서, jwt token 검증 메서드
    public boolean validToken(String token){

        try{
            makeParser()
                    .parseClaimsJws(token);
            return true;
        } catch (Exception e){
            if (e instanceof SignatureException){
                throw new TokenException(TokenErrorCode.INVALID_TOKEN);
            }
            else if (e instanceof ExpiredJwtException) {
                // 만료된 토큰
                throw new TokenException(TokenErrorCode.EXPIRED_AT,e);
            }
            else {
                // 그 외 처리
                throw new TokenException(TokenErrorCode.INVALID_TOKEN,e);
            }
        }
    }

🤔1. ExceptionHandler에서 처리

  • 우리는 항상 예외처리를 하면, ExceptionHandler로 예외를 보내고
  • @RestControllerAdvic@ExceptionHandler을 통해서 예외를 처리하였다
  • 아무 의심 없이 filter에서 던져지는 예외를 ExceptionHandler에서 처리를 하기 위해
  • TokenExceptionHandler를 exceptionHandler경로에 만들어 주었다
@Slf4j
@RestControllerAdvice
@Order(value = Integer.MIN_VALUE)
public class TokenExceptionHandler {

    @ExceptionHandler(value = TokenException.class)
    public ResponseEntity<Api<Object>> apiException(TokenException tokenException){

        log.error("", tokenException);

        ErrorCodeIfs errorCodeIfs = tokenException.getErrorCodeIfs();

        return ResponseEntity
                .status(errorCodeIfs.getHttpStatusCode())
                .body(
                        Api.Error(errorCodeIfs, tokenException.getErrorDescription())
                );
    }
}
  • 다음과 같이 @ExceptionHandler에서 TokenException.class를 처리 하는 예외 처리 handler을 만들었다
  • 만료기간이 지난 토큰을 보내고 원하는 Api 스펙을 기대하였다
  • 하지만... 결과는...
  • 아무런 응답 메시지도 오지 않았다
  • log에는 TokenError 가 잘나는데... 왜 그렇지?
  • 혹시 몰라 JwtFilter에 🤔디버깅을 해서, TokenException이 잘 나오는 것도 확인하였다
  • 원하는 예외는 잘 일어났지만, 예외 처리를 handler에서 잘 처리하지 못하고 있다고 판단 하였다
  • 다시 TokenExceptionHandler 코드를 잘 봐보았더니... RestControllerAdvice가 눈에 보였다
  • 아 설마, 컨트롤러 딴의 예외만 잡아주는 걸까? filter에서의 예외는 처리를 하지 못하는건가??? 라는 생각을 가지고 먼저, @RestControllerAdvice의 스펙을 확인하였다
  • 역시 컨트롤러에서의 예외를 잡아주었다.
  • 더 찾아보니, ✅핸들러를 사용한 예외처리는 DispatcherServlet에 의해 처리되는 경우 작동한다고 한다.
  • ✅Security FilterDispatcherServlet에 도달하기 전에 먼저 거쳐지는 곳이기 때문에 @ControllerAdvice에서 예외처리를 해줄 수 없는 것이다.

filter 측에서 예외처리를 한 후, 예외를 처리한 후 DispatcherServlet으로 넘겨줘야 된다고 판단하였다!

🤔2. JwtFilter안에서 try,catch

@Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        // request에서 Authorization 헤더를 찾기
        String authorization = request.getHeader(HEADER_AUTHORIZATION);

        // Authorization 헤더 검증 -> jwt token인지
        if (authorization == null || !authorization.startsWith("Bearer ")){
            log.info("token null");
            filterChain.doFilter(request,response);
            // 다음 조건이 해당하면 -> 메서드 종료
            return;
        }

        // 가져온 값에서 접두사 제거 -> 토큰 꺼내 오기
        String token = getAccessToken(authorization);

        if (jwtUtil.validToken(token)){

            // 토큰에서 username, role 획득
            String email = jwtUtil.getUsername(token);
            String role = jwtUtil.getRole(token);

            // userEntity를 생성해서 해당 값을 넣어준다
            UserEntity userEntity = UserEntity.builder()
                    .email(email)
                    .role(role)
                    .password("temppassword")
                    .build();

            //UserDetails에 회원 정보 객체 담기
            CustomUserDetails customUserDetails = new CustomUserDetails(userEntity);

            //스프링 시큐리티 인증 토큰 생성
            Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());

            //세션에 사용자 등록
            SecurityContextHolder.getContext().setAuthentication(authToken);
        }


        filterChain.doFilter(request, response);
    }
  • 결국 지금 예외가 일어나는 validToken()부분을 try, catch문으로 감싼 후,
  • 예외가 일어나면 해당 예외 메시지를 보내주자
  • 여러 오류가 있으므로, 메서드를 만들고 적용하자 라는 생각을 하였다!
  • 그렇게 열심히 코드를 작성하다 보니,,,
  • 결국 return에서 Api스펙에 맞게 ResponseEntity를 보내든지, 내가 원하는 객체를 보내줘야한다....
  • SpringSecurity의 기술을 의존해서 사용하고 있는데, 🤔굳이 새로운 반환 타입을 주는 메서드를 만들고 복잡하게 사용해야 될까? 라는 생각을 하였다
  • 또 결국 dto를 사용하면, 결국 DispatcherServlet으로 넘어가는게 아닐까 라는 생각을 하였다
  • 뭔가 🤔SpringSecurity의 filter에 대해 오류를 처리할 수 있는 방법이 있을거라는 생각이 들었다!

✅3. 예외 처리 Filter 생성

  • 우리는 생각을 해보면, 여러 filter들이 연속적으로 doFilter을 통해서 계속 순차적으로 작동되는 사실을 알고 있다

  • 결국 예외처리를 위한 Filter을 만들어 커스텀 할 수 있다는 소리였다!

그러므로 ✅Filter에서 발생하는 예외를 핸들링하려면, 예외 발생이 예상되는 Filter의 ✅상위에 예외를 핸들링하는 Filter를 만들어서 ✅Filter Chain에 추가해주면 된다

예외처리를 위한 JwtExceptionHandler

@Component
public class JwtExceptionHandler extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            filterChain.doFilter(request,response);
        } catch (TokenException e){
            response.setStatus(e.getErrorCodeIfs().getHttpStatusCode());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding("UTF-8");
            String error = objectMapper.writeValueAsString(Result.Error(e.getErrorCodeIfs()));
            response.getWriter().write(error);

        }
    }
}

SecurityConfig에 filter 추가

  • 이 필터에서는 TokenException이 발생한다면, 예외에서 원하는 정보를 뽑은 후
  • response로 ObjectMapper를 이용해서 원하는 스펙을 보내기로 하였다
  • 그리고 미리 공통 Api 스팩으로 만들어둔 Result에 값을 넣어서 반환해주기로 하였다
.authorizeHttpRequests((auth) -> auth
                        .requestMatchers("/login", "/", "/join").permitAll()
                        .requestMatchers("/admin").hasRole("ADMIN")
                        .anyRequest().authenticated())
                .addFilterBefore(new JwtFilter(jwtUtil), LoginFilter.class)
                .addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new JwtExceptionHandler(), JwtFilter.class)
                // jwt는 세션을 stateless하게 관리한다
                .sessionManagement((session) -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .build();
  • 그리고 정해진 스펙대로, JwtFilter 발생하는 예외를 잡기위해서
  • ✅이전에 예외처리를 위한 JwtExceptionHandler 필터를 추가해준다!
  • addFilterBefore을 사용하였다!

만료된 토큰으로 요청

  • 만료된 토큰으로 요청을 하였고
  • 원하는 스펙의 예외처리가 잘 되었나 확인해 보겠다
  • 우리가 원하는대로, ✅result의 형식대로 Api응답이 잘 나왔다!!!
profile
개발 공부,정리

0개의 댓글