본문 바로가기

Spring Boot/Security

커스텀 필터 구현 : 구현 방법

 - 커스텀 필터를 직접 구현하는 방법

 - GenericFilterBean을 확장하여 필터를 구현하는 방법

 - OncePerRequestFilter를 활용하여 요청당 한번만 실행되는 필터 작성하기

 - 필터 내부에서 요청 / 응답 객체를 다루고 원하는 로직을 삽입하는 방법

 - 실무에서 자주 사용되는 커스텀 필터 패턴 예시


커스텀 필터 구현 방법

1. GenericFilterBean 확장

Spring Security에서 필터를 구현할 때 가장 기본적으로 사용할 수 있는 클래스

javax.servlet.Filter 를 구현한 추상 클래스이며, 스프링 빈으로 등록 가능하도록 지원한다

 

CustomGenericFilter 클래스, GenericFilterBean을 상속함

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import org.springframework.web.filter.GenericFilterBean;

import java.io.IOException;

public class CustomGenericFilter extends GenericFilterBean { // GenericFilterBean을 extends 한 다음
    // doFilter 메서드만 구현하면 됨
    @Override
    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        // 요청 전 처리 로직
        System.out.println("[CustomGenericFilter] 요청 처리 전 실행하기");

        // 필터 체인 진행
        filterChain.doFilter(servletRequest, servletResponse);

        // 응답 후 처리 로직
        System.out.println("[CustomGenericFilter] 응답 처리 후 실행하기");
    }
}

 

요청이 들어올때와 응답이 나갈 때 모두 원하는 로직을 삽입할 수 있다

 

 

(1) 실무 활용 예(GenericFilterBean)

사용자 세션 만료 처리 : 로그인은 되어 있지만, 세션 만료 시간이 지난 경우 자동으로 로그아웃 처리됨

IP 화이트리스트 : 특정 API는 내부망 IP에서만 접근하도록 제한하게끔 함

        // 요청 전 처리 로직
        System.out.println("[CustomGenericFilter] 요청 처리 전 실행하기");

        // 전처리
        String clientIp = servletRequest.getRemoteAddr();
        List<String> whiteList = List.of("127.0.0.1", "192.168.0.10");
        if(!whiteList.contains(clientIp)) {
            servletResponse.setContentType("text/plain;charset=utf-8");
            servletResponse.getWriter().write("허용되지 않은 IP입니다.");
            return; // 꼭 해줘야함, 필터 체인 중단
        }

 

GenericFilterBean은 요청과 응답을 모두 다루며, 전후 처리 제어가 필요한 경우 주로 사용된다

 

 

 

2. OncePerRequestFilter 활용하기

Security에서 커스텀 필터를 구현할 때 가장 많이 사용하는 클래스임

하나의 요청당 반드시 한번만 실행되도록 보장한다

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

public class CustomOncePerRequestFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        /**
         * 요청 전 처리
         * 클라이언트의 IP를 가져온다
         * OS 레벨의 TCP 이후 tomcat을 통해 IP를 가져옴
         */
        String clientIp = request.getRemoteAddr();
        System.out.println("[CustomOncePerRequestFilter] 클라이언트 IP : " + clientIp);

        // 필터 체인 진행
        filterChain.doFilter(request, response);

        // 응답 후 처리
        System.out.println("[CustomOncePerRequestFilter] 응답 완료");
    }
}

 

주로 인증 / 인가 검증, 로깅, 헤더 검증 등의 작업에서 활용된다

 

 

 

(1) 실무 활용 예시(OncePerRequestFilter)

JWT 토큰 검증 : 요청 헤더에 포함된 토큰을 파싱하고 유효성을 검사한 뒤, 인증 객체를 SecurityContext에 저장함

        String clientIp = request.getRemoteAddr();
        System.out.println("[CustomOncePerRequestFilter] 클라이언트 IP : " + clientIp);
        
        String token = request.getHeader("Authorization");
        if(token != null && token.startsWith("Bearer")) {
            // 토큰 파싱 및 검증 로직 구현
            // 검증에 성공하면 Authentication 객체 생성 후 SecurityContext에 저장하는 로직
        }

 

 

요청 추적 ID 로깅 : MSA 환경에서 모든 요청에 공통 Trace ID를 심어서 로그를 추적함

        // 전처리 일부
        String token = request.getHeader("Authorization");
        if(token != null && token.startsWith("Bearer")) {
            // 토큰 파싱 및 검증 로직 구현
            // 검증에 성공하면 Authentication 객체 생성 후 SecurityContext에 저장하는 로직
        }
        
        // 이 부분
        String traceId = UUID.randomUUID().toString();
        response.getHeader("X-Trace-Id" + traceId);
        System.out.println("[Trace] 요청 추적 ID : " + traceId);

 

OncePerRequestFilter는 요청마다 반드시 한번 실행되어야 하는 보안 / 로그 / 추적 로직에 적합하다

 

 

 

3. 필터 로직 구현

커스텀 필터 안에서는 요청과 응답 객체를 자유롭게 다룰 수 있다

 

(1) 요청(Request) 활용 예시

        String token = request.getHeader("Authorization");
        if(token == null && token.startsWith("Bearer ")) {
            // 토큰 파싱 및 검증 로직 구현
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return; // 인증에 실패하면 필터체인이 중단됨

            // 검증에 성공하면 Authentication 객체 생성 후 SecurityContext에 저장하는 로직
        }

 

 

(2) 응답 활용 예시

        String traceId = UUID.randomUUID().toString();
        response.addHeader("X-Trace-Id", traceId);
        
        // 여기
        response.addHeader("X-Custom-Header", "CustomValue");
        System.out.println("[Trace] 요청 추적 ID : " + traceId);

 

이런식으로 요청 헤더를 검사하거나 응답 헤더를 추가하여 보안을 강화할 수 있다

 


  • GenericFilterBean 은 단순히 필터 구조를 직접 제어해야할 때 유용하다
  • OncePerRequestFilter 대부분의 경우 추천되며, 중복 실행 방지가 필요한 로직에 적합하다
  • 필터 내부에서 무거운 연산을 수행하면 전체 요청 처리 성능이 저하될 수 있다. 주의해야함
  • 예외가 발생하면 적절히 처리해야함. "전체" 애플리케이션이 에러 응답을 반환할 수 있다