본문 바로가기

Spring Boot

[Spring Boot] Filter란? 필터를 활용한 요청 및 응답 로깅

728x90

 

 

Filter란?

Filter는 클라이언트의 요청과 응답을 가로채는 역할을 합니다. 스프링 프레임워크에서는 필터가 DispatcherServlet 이전과 이후에 동작하며, 웹 컨텍스트 영역에서 실행됩니다. 스프링 부트에서는 기본적으로 톰캣 서버가 제공되며, 필터는 톰캣 서버와 DispatcherServlet 사이의 요청 및 응답을 제어할 수 있습니다.

 

 

Filter의 활용

  1. 데이터 변환 및 조작
    • 요청 또는 응답 데이터를 변환하거나 가공합니다.
  2. 요청 로깅 (Logging)
    • 클라이언트의 요청 데이터를 기록합니다.
  3. 인증 및 권한 확인
    • 요청에 대한 인증권한을 필터 단계에서 확인합니다.
  4. 필터 체인(Filter Chain) 구성
    • 여러 필터를 연속적으로 실행해 특정 작업을 처리할 수 있습니다.

 

 

예시: 학생 등록 시스템에서 발생할 수 있는 문제

1. 문제 상황

클라이언트가 아래와 같은 데이터를 서버에 요청한다고 가정합니다.

{
    "name": "짱구",
    "no": "2020001",
    "phone": "010-1111-2222",
    "email": "JJang@gmail.com"
}

 

서버에서는 StudentRequest 객체로 데이터를 매핑하려고 하지만 다음과 같은 오류가 발생합니다.

StudentRequest(name=짱구, no=2020001, phoneNumber=null, email=JJang@gmail.com)

문제점:
클라이언트가 보낸 필드 이름이 phone인데, 서버는 phoneNumber로 정의했습니다. 이처럼 서로 매핑되지 않아 값이 누락됩니다.
이 문제를 해결하려면 클라이언트가 보낸 원본 데이터를 확인할 필요가 있습니다.

 

 

 

클라이언트의 요청 데이터 확인 방법

1. HttpEntity 사용

HttpEntity는 요청의 본문과 헤더를 포함합니다.

@Slf4j
@RestController
public class StudentController {

    @PostMapping("/student/register")
    public void register(HttpEntity httpEntity) {
        log.info("{}", httpEntity);
    }
}

요청 결과:

<{name=짱구, no=2020001, phone=010-1111-2222, email=JJang@gmail.com}, 
[user-agent:"PostmanRuntime/7.42.0", content-length:"107", ...]>

 

 

2. 에코 응답하기

요청 데이터를 그대로 응답하여 문제를 파악합니다.

@Slf4j
@RestController
public class StudentController {

    @PostMapping("/student/register")
    public StudentRequest register(@RequestBody StudentRequest studentRequest) {
        log.info("{}", studentRequest);
        return studentRequest;  // 요청 데이터를 그대로 반환
    }
}

 

이 방법은 어느 정도 유용하지만, 문제를 명확히 파악하기 어려울 수 있습니다.

 

 

3. Filter 사용하기

필터를 사용하면 요청과 응답 데이터를 명확하게 추적할 수 있습니다.

LoggerFilter 구현

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import java.io.IOException;

@Slf4j
@Component
public class LoggerFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {

        // 요청과 응답을 감싸는 래퍼 생성
        var req = new ContentCachingRequestWrapper((HttpServletRequest) servletRequest);
        var res = new ContentCachingResponseWrapper((HttpServletResponse) servletResponse);

        // DispatcherServlet 전
        filterChain.doFilter(req, res);
        // DispatcherServlet 후

        // 요청과 응답 데이터 추출
        var reqJson = new String(req.getContentAsByteArray(), servletRequest.getCharacterEncoding());
        var resJson = new String(res.getContentAsByteArray(), servletResponse.getCharacterEncoding());

        log.info("Request: {}", reqJson);
        log.info("Response: {}", resJson);

        // 응답 데이터 복원
        res.copyBodyToResponse();
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

설명 및 주의 사항

  1. ContentCachingRequestWrapper와 ContentCachingResponseWrapper 사용 이유
    • 기본적으로 HttpServletRequest와 HttpServletResponse 객체는 스트림을 한 번만 읽을 수 있습니다.
    • 데이터를 여러 번 읽기 위해 ContentCachingWrapper를 사용해 캐시된 데이터를 추출합니다.
  2. res.copyBodyToResponse() 호출 필요성
    • 응답 데이터를 읽은 후에는 반드시 복원해야 합니다. 그렇지 않으면 클라이언트가 응답을 받을 수 없습니다.
  3. Character Encoding
    • 문자열로 변환할 때 인코딩을 명시적으로 설정하여 데이터의 무결성을 유지합니다.

 

 

정리

필터는 클라이언트의 요청과 서버의 응답을 가로채어 로그를 남기거나 데이터의 무결성을 확인하는 데 매우 유용합니다. 특히 로깅이나 인증 처리에 유용하며, ContentCachingWrapper를 사용하면 요청과 응답을 안전하게 복원할 수 있습니다.

728x90