트러블 슈팅 - 코드 리팩토링

Zyoon·2025년 6월 11일

트러블슈팅

목록 보기
4/11
post-thumbnail

❗메서드의 문제를 파악하여 리팩토링 후 비교


JWT 필터 메서드 → 리팩토링 진행

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    String url = httpRequest.getRequestURI();

    if (url.startsWith("/auth")) {
        chain.doFilter(request, response);
        return;
    }

    String bearerJwt = httpRequest.getHeader("Authorization");

    if (bearerJwt == null) {
        // 토큰이 없는 경우 400을 반환합니다.
        httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "JWT 토큰이 필요합니다.");
        return;
    }

    String jwt = jwtUtil.substringToken(bearerJwt);

    try {
        // JWT 유효성 검사와 claims 추출
        Claims claims = jwtUtil.extractClaims(jwt);
        if (claims == null) {
            httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "잘못된 JWT 토큰입니다.");
            return;
        }

        UserRole userRole = UserRole.valueOf(claims.get("userRole", String.class));

        httpRequest.setAttribute("userId", Long.parseLong(claims.getSubject()));
        httpRequest.setAttribute("email", claims.get("email"));
        httpRequest.setAttribute("userRole", claims.get("userRole"));

        if (url.startsWith("/admin")) {
            // 관리자 권한이 없는 경우 403을 반환합니다.
            if (!UserRole.ADMIN.equals(userRole)) {
                httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "관리자 권한이 없습니다.");
                return;
            }
            chain.doFilter(request, response);
            return;
        }

        chain.doFilter(request, response);
    } catch (SecurityException | MalformedJwtException e) {
        log.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.", e);
        httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "유효하지 않는 JWT 서명입니다.");
    } catch (ExpiredJwtException e) {
        log.error("Expired JWT token, 만료된 JWT token 입니다.", e);
        httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "만료된 JWT 토큰입니다.");
    } catch (UnsupportedJwtException e) {
        log.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.", e);
        httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "지원되지 않는 JWT 토큰입니다.");
    } catch (Exception e) {
        log.error("Invalid JWT token, 유효하지 않는 JWT 토큰 입니다.", e);
        httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "유효하지 않는 JWT 토큰입니다.");
    }
}

1. 문제점

  • 현재 doFilter() 메서드에 너무 많은 로직이 포함되어 있다. (JWT 추출, 검증, 권한 체크 등)
  • 단일 책임 원칙(SRP)에 위배되며 유지보수성과 가독성이 떨어지게 된다는 문제가 발생한다.

2. 해결 방안

코드 재 설계

  • doFilter의 책임을 명확히 "요청 흐름 제어"로만 한정한다.
  • 세부 로직은 별도 메서드로 분리하여 관리한다. (JWT 추출, 검증, 권한 체크 등)
  • "하나의 책임 = 하나의 메서드" 원칙을 적용하여 각 메서드가 하나의 역할 당당하도록 명시한다.

해결 과정

  • isAuthUri(): 인증 예외 URL 판단
private boolean isAuthUri(String uri) {
	  return uri.startsWith("/auth");
}
  • extractToken(): 헤더에서 토큰 추출 및 가공
private String extractToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
    String bearerToken = request.getHeader("Authorization");
    if (bearerToken == null) {
        response.sendError(HttpServletResponse.SC_BAD_REQUEST, "JWT 토큰이 필요합니다.");
        return null;
    }
    return jwtUtil.substringToken(bearerToken);
}
  • validateToken(): JWT 유효성 검사 및 claims 추출
    private Claims validateToken(String token, HttpServletResponse response) throws IOException {
        try {
            return jwtUtil.extractClaims(token);
        } catch (SecurityException | MalformedJwtException e) {
            log.error("유효하지 않은 JWT 서명", e);
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "유효하지 않는 JWT 서명입니다.");
        } catch (ExpiredJwtException e) {
            log.error("만료된 JWT 토큰", e);
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "만료된 JWT 토큰입니다.");
        } catch (UnsupportedJwtException e) {
            log.error("지원되지 않는 JWT 토큰", e);
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "지원되지 않는 JWT 토큰입니다.");
        } catch (Exception e) {
            log.error("JWT 검증 실패", e);
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "유효하지 않는 JWT 토큰입니다.");
        }
        return null;
    }
  • setUserAttributes(): claims → request attribute 설정
private void setUserAttributes(HttpServletRequest request, Claims claims) {
    request.setAttribute("userId", Long.parseLong(claims.getSubject()));
    request.setAttribute("email", claims.get("email"));
    request.setAttribute("userRole", claims.get("userRole"));
}
  • authorizeAdminAccess(): 관리자 URL 접근 시 권한 확인
private boolean authorizeAdminAccess(String requestURI, HttpServletRequest request, HttpServletResponse response) throws IOException {
    String userRole = (String) request.getAttribute("userRole");

    if (requestURI.startsWith("/admin") && !UserRole.ADMIN.name().equals(userRole)) {
        response.sendError(HttpServletResponse.SC_FORBIDDEN, "관리자 권한이 없습니다.");
        return false;
    }
    return true;
}
  • 리팩토링 후 doFilter()흐름 조정만 담당
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    String requestURI = httpRequest.getRequestURI();

    //auth url 판단
    if (isAuthUri(requestURI)) {
        chain.doFilter(request, response);
        return;
    }

    //토큰 추출
    String token = extractToken(httpRequest, httpResponse);
    if (token == null) return;

    //Claims 생성 및 정합성 검사
    Claims claims = validateToken(token, httpResponse);
    if (claims == null) return;

    //Attribute 에 유저 정보 저장
    setUserAttributes(httpRequest, claims);

    //관리자 권한 여부 판단
    if (!authorizeAdminAccess(requestURI, httpRequest, httpResponse)) {
        return;
    }

    chain.doFilter(request, response);
}

3. 해결 완료

회고

  • 각 기능을 메서드로 분리해 가독성이 크게 향상 되었다.
  • 예외 처리가 구체적이고 명확한 위치에서 이뤄지게 되었다.
  • 수정 및 추가 시, 한 기능만 수정할 수 있게 되어 유지보수가 용이해졌다.
  • 전체 흐름이 한눈에 들어와 디버깅이 수월해졌다.
profile
기어 올라가는 백엔드 개발

0개의 댓글