Spring/강의

[📙 숙련 Spring] 2-4. Filter

가지코딩 2025. 5. 20. 22:21

📙 목차

  1. 공통 관심 사항(cross-cutting concerns)
  2. Servlet Filter
  3. Servlet Filter 구현 방법
  4. Servlet Filter 구현 및 등록 예제
  5. [실습]

1. 공통 관심사(cross-cutting concerns)

공통 관심사(cross-cutting concerns, 횡단 관심사 )

  • 비즈니스 로직과는 별개로 여러 기능에서 공통적으로 필요한 처리 로직
  • 여러 클래스나 모듈에서 반복적으로 사용되는 부가적인 기능
  • 비즈니스 로직에 직접 넣으면 중복 코드, 관심사 분리 실패, 유지보수 어려움 등의 문제가 발생한다.
  • ex. 인증, 인가, 로깅, 인코딩 처리
@PostMapping("/post")
public PostResponseDto create(PostCreateRequestDto request) {
    // 로그인 여부 확인 -> 공통 관심사
    // 글 생성 로직 -> 비즈니스 로직
}

 

 

공통 관심사 분리 방법

방법 적용 시점 주요 용도 특징
Filter 서블릿 요청 전/후 인코딩, 인증, 로깅 등 DispatcherServlet 이전 처리
Interceptor 컨트롤러 호출 전/후 로그인 인증, 권한 체크 HandlerMapping 이후 처리
AOP 메서드 실행 전/후/예외 트랜잭션, 로깅, 예외처리 등 비즈니스 로직 전후 세밀한 제어
Proxy 패턴 객체 접근 시 서비스 계층에서 주로 사용 클래스 구조로 해결, 확장성 용이

 

더보기

Filter - 서블릿 단계에서 처리

  • 서블릿 컨테이너 레벨에서 작동
  • 모든 요청에 대해 전처리/후처리 가능
  • Spring의 DispatcherServlet 이전 단계
@WebFilter(urlPatterns = "/*")
public class LogFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        System.out.println("Request Logging...");
        chain.doFilter(request, response);
    }
}

 

 

Interceptor - Spring MVC 요청 처리 중간

  • 컨트롤러 호출 전/후에 개입 가능
  • 인증/인가, 사용자 세션 확인 등에 유용
  • View 렌더링 직전까지 개입 가능
public class AuthInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 로그인 여부 확인
        return true;
    }
}

 

 

AOP (Aspect-Oriented Programming) - 메서드 단위 세밀 제어

  • @Before, @After, @Around 등을 이용해 메서드 단위로 공통 로직 삽입
  • 트랜잭션 처리, 로깅, 성능 측정 등에 활용
  • 비즈니스 로직과 완전히 분리 가능
@Aspect
@Component
public class LogAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("메서드 호출 전 로깅: " + joinPoint.getSignature());
    }
}

 

 

프록시 패턴 (Proxy Pattern)

  • 객체 앞단에 대리 객체(Proxy)를 세워 공통 로직 수행
  • 객체 구조 기반이라 AOP 미적용 환경에서도 활용 가능
public class LoggingServiceProxy implements UserService {
    private final UserService target;

    public LoggingServiceProxy(UserService target) {
        this.target = target;
    }

    public void join(User user) {
        System.out.println("회원 가입 시작");
        target.join(user);
        System.out.println("회원 가입 완료");
    }
}

 

언제 어떤 방법을 써야 할까?

상황 추천 방법
요청/응답 전처리 필요 (예: 인코딩, 인증) Filter
컨트롤러 실행 전후에 제어 필요 Interceptor
서비스/DAO에서 로깅, 트랜잭션 등 적용 AOP
프레임워크 없이 공통 로직 분리 필요 Proxy 패턴

2. Servlet Filter

Servlet Filter

  • HTTP 요청/응답을 가로채 전처리 또는 후처리할 수 있는 컴포넌트
  • 요청 전처리 (Request Logging, 인증, 인코딩 설정 등)
  • 응답 후처리 (Response 압축, 응답 로그 등)

 

 

Servlet Filter 특징

  • 공통 관심사 로직 처리
    • 공통된 로직을 중앙 집중적으로 구현하여 재사용성이 높고 유지보수가 쉽다.
    • 모든 요청이 하나의 입구를 통해 처리되어 일관성을 유지한다.
  • HTTP 요청 및 응답 필터링
  • Filter Chain
    • 여러 개의 필터가 순차적으로 적용될 수 있다.
    • filterChain.doFilter(request, response); 다음 필터로 제어를 전달한다.
  • doFilter()
    • 실제 필터링 작업을 수행하는 주요 메소드로 필터가 처리할 작업을 정의한다.
    • 다음 필터로 제어를 넘길지 여부를 결정한다.

 

 

Filter 동작 순서

  • Filter를 적용하면 Servlet이 호출되기 이전에 Filter를 항상 거치게된다.
  • 공통 관심사를 필터에만 적용하면 모든 요청 or 응답에 적용된다 .
  • Filter는 특정 URL Pattern에 적용할 수 있다.
  • Spring을 사용하는 경우 Servlet은 Dispatcher Servlet이다.

Filter Chain


3. Servlet Filter 구현 방법

Filter Interface

  • Filter Interface를 Implements하여 구현하고 Bean으로 등록하여 사용한다.
    • Servlet Container가 Filter를 Singleton 객체로 생성 및 관리한다.

 

 

Filter Interface 주요 메서드

  • init()
    • Filter를 초기화하는 메서드이다.
    • Servlet Container가 생성될 때 호출된다.
    • default method이기 때문에 implements 후 구현하지 않아도 된다.
  •  doFilter()
    • Client에서 요청이 올 때 마다 doFilter() 메서드가 호출된다.
      • doFilter() 내부에 필터 로직(공통 관심사 로직)을 구현하면 된다.
    • WAS에서 doFilter() 를 호출해주고 하나의 필터의 doFilter()가 통과된다면
    • Filter Chain에 따라서 순서대로 doFilter() 를 호출한다.
    • 더이상 doFilter() 를 호출할 Filter가 없으면 Servlet이 호출된다.
  • destroy()
    • 필터를 종료하는 메서드이다.
    • Servlet Container가 종료될 때 호출된다.
    • default method이기 때문에 implements 후 구현하지 않아도 된다.

 

주요 메서드 정리

메서드 시점 목적 구현 필수 여부
init() 필터 생성 시 1회 필터 초기화 작업 선택 구현
doFilter() 요청마다 반복 호출 요청/응답 전후 처리 (핵심) 필수 구현
destroy() 필터 종료 시 1회 자원 정리 선택 구현

4. Servlet Filter 구현 및 등록 예제

Filter 구현체: 요청 URL 로그 출력

  • doFilter() 메서드는 요청마다 실행되며, 로그 출력 후 다음 필터나 서블릿으로 제어를 넘긴다.
  • ServletRequest는 기능이 제한적이므로, 일반적으로 HttpServletRequest로 다운캐스팅하여 사용한다.
  • 필터 체인에 더 이상 필터가 없으면 최종적으로 DispatcherServlet 또는 서블릿이 호출된다.
@Slf4j
public class CustomFilter implements Filter {
    @Override
    public void doFilter(
            ServletRequest request,
            ServletResponse response,
            FilterChain chain
    ) throws IOException, ServletException {

        // HttpServletRequest로 다운캐스팅
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();

        log.info("request URI={}", requestURI);

        // 다음 Filter 또는 서블릿 호출
        chain.doFilter(request, response);
    }
}

 

 

Filter 등록: FilterRegistrationBean 사용

  • setFilter() : 등록할 필터를 지정한다.
  • setOrder() : 필터 체인 순서를 지정한다. 숫자가 낮을수록 먼저 실행된다.
  • addUrlPatterns("/*") : 모든 요청에 대해 해당 필터를 적용한다.
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public FilterRegistrationBean<Filter> customFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();

        // 1. 필터 등록
        filterRegistrationBean.setFilter(new CustomFilter());

        // 2. 필터 실행 순서 설정 (낮을수록 우선순위 ↑)
        filterRegistrationBean.setOrder(1);

        // 3. URL 패턴 지정 - 전체 요청에 대해 필터 적용
        filterRegistrationBean.addUrlPatterns("/*");

        return filterRegistrationBean;
    }
}

 

 

정리

항목 설명
Filter 인터페이스 요청 전/후에 로직을 삽입할 수 있는 Servlet 컴포넌트
doFilter() 요청 처리의 핵심 메서드. 필수 구현
FilterRegistrationBean 필터를 Spring Boot에 등록하기 위한 클래스
URL 패턴 지정 addUrlPatterns()로 지정. "/*"는 전체 요청에 필터 적용
필터 우선순위 setOrder()로 지정. 숫자가 낮을수록 먼저 실행됨
Controller보다 먼저 실행됨 존재하지 않는 URL이어도 필터는 동작 → 로깅, 보안 등에 적합함

5. [실습]

요구사항1: Cookie/Session 방식으로 로그인 기능을 구현한다.(가정)
요구사항2: 로그인 인증은 Servlet Filter로 구현한다.
요구사항3: /, /user/signup, /login, /logout URL로 들어오는 요청은 인증 Filter 로직을 수행하지 않도록 해야한다.

 

 

* spring-filter 실습 파일에서 진행

 

GitHub - UK-spring/spring-filter: Spring 숙련주차 Filter

Spring 숙련주차 Filter. Contribute to UK-spring/spring-filter development by creating an account on GitHub.

github.com

 

 

Login Filter 만들기

  • /filter/LoginFilter.java
@Slf4j
public class LoginFilter implements Filter {
    private static final String[] WHITE_LIST = {"/", "/user/signup", "/login", "/logout"};

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        String requestURI = httpRequest.getRequestURI();

        HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;

        log.info("로그인 필터 로직 실행");

        if(!isWhiteList(requestURI)) {
            HttpSession session = httpRequest.getSession(false);

            if(session == null || session.getAttribute("sessionKey") == null) {
                throw new RuntimeException("로그인 해주세요.");
            }

            log.info("로그인에 성공했습니다.");
        }

        // 다음 필터가 없으면 Servlet -> Controller, 다음 필터가 있으면 다음 Filter 호출
        filterChain.doFilter(servletRequest, servletResponse);
    }

    private boolean isWhiteList(String requestURI) {
        return PatternMatchUtils.simpleMatch(WHITE_LIST, requestURI);
    }
}

 

 

Filter 등록하기

  • /config/WebConfig.java
@Configuration
public class WebConfig implements WebMvcConfigurer {
	...
    
    @Bean
    public FilterRegistrationBean loginFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LoginFilter());
        filterRegistrationBean.setOrder(2);
        filterRegistrationBean.addUrlPatterns("/*");

        return filterRegistrationBean;
    }
}

 

 

결과

 

 

 

동작 순서