728x90
Filter란?
Filter는 클라이언트의 요청과 응답을 가로채는 역할을 합니다. 스프링 프레임워크에서는 필터가 DispatcherServlet 이전과 이후에 동작하며, 웹 컨텍스트 영역에서 실행됩니다. 스프링 부트에서는 기본적으로 톰캣 서버가 제공되며, 필터는 톰캣 서버와 DispatcherServlet 사이의 요청 및 응답을 제어할 수 있습니다.
Filter의 활용
- 데이터 변환 및 조작
- 요청 또는 응답 데이터를 변환하거나 가공합니다.
- 요청 로깅 (Logging)
- 클라이언트의 요청 데이터를 기록합니다.
- 인증 및 권한 확인
- 요청에 대한 인증과 권한을 필터 단계에서 확인합니다.
- 필터 체인(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();
}
}
설명 및 주의 사항
- ContentCachingRequestWrapper와 ContentCachingResponseWrapper 사용 이유
- 기본적으로 HttpServletRequest와 HttpServletResponse 객체는 스트림을 한 번만 읽을 수 있습니다.
- 데이터를 여러 번 읽기 위해 ContentCachingWrapper를 사용해 캐시된 데이터를 추출합니다.
- res.copyBodyToResponse() 호출 필요성
- 응답 데이터를 읽은 후에는 반드시 복원해야 합니다. 그렇지 않으면 클라이언트가 응답을 받을 수 없습니다.
- Character Encoding
- 문자열로 변환할 때 인코딩을 명시적으로 설정하여 데이터의 무결성을 유지합니다.
정리
필터는 클라이언트의 요청과 서버의 응답을 가로채어 로그를 남기거나 데이터의 무결성을 확인하는 데 매우 유용합니다. 특히 로깅이나 인증 처리에 유용하며, ContentCachingWrapper를 사용하면 요청과 응답을 안전하게 복원할 수 있습니다.
728x90
'Spring Boot' 카테고리의 다른 글
[Spring Boot] 핸들러 인터셉터(Handler Interceptor) (0) | 2024.10.14 |
---|---|
[Spring Boot] Spring MVC 아키텍처: 요청 처리 흐름과 주요 컴포넌트 (0) | 2024.10.14 |
[Spring Boot] Pageable 사용하기 (1) | 2024.10.10 |
[Spring Boot] 여러가지 Annotation (2) | 2024.10.10 |
[Spring Boot] JPA 연관 관계 설정하기 (1) | 2024.10.10 |