Filter & Interceptor

쉬운 목차

Filter

Filter란?

Filter는 클라이언트의 요청이 서블릿에 도달하기 전(pre-processing)과 응답이 클라이언트에게 돌아가기 전(post-processing)에 작업을 수행합니다.

  • 사용자 인증, 요청 정보 로깅, 데이터 암/복호화, 헤더 검사(XSS 방어) 등과 같은 전처리 또는 후처리 작업에 사용됩니다.
  • Filter는 서블릿 컨테이너(Tomcat 등)에 의해 관리됩니다. 이는 스프링의 빈(Bean)과는 다른 관리 영역이라는 의미입니다. 따라서 Filter에서 스프링의 의존성 주입(Dependency Injection)과 같은 특정 스프링 기능을 직접 사용하기 어렵습니다. 하지만 스프링에서는 Filter를 빈으로 등록하여 스프링의 일부 기능을 활용할 수 있습니다.

Body 수정에 대한 주의:

  • Request body를 수정할 때에는 주의가 필요합니다. 그러나 Request body의 데이터는 InputStream에 있기 때문에, 한 번 읽고 나면 다시 읽을 수 없습니다. 이로 인해 실제로는 Request body를 수정하기 어려울 수 있습니다. 이 문제를 해결하기 위해서는 ServletRequestWrapperHttpServletRequestWrapper를 사용하여 요청을 래핑하고, 래핑된 객체에서 새로운 InputStream을 제공하는 방식을 사용해야 합니다.

예외 처리:

  • Filter에서의 예외 처리는 직접 구현해야 합니다. 서블릿 컨테이너는 일반적으로 Filter에서 발생한 예외를 적절히 처리하지 않으므로, 예외 처리는 개발자에게 달려 있습니다.

대표적인 사용 예시

  • 로그인 인증 필터
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Value("${jwt.secret}")
    private String jwtSecret; // application.properties 또는 application.yml에서 JWT 시크릿 키 설정

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // HTTP 헤더에서 토큰을 가져오기
        String header = request.getHeader("Authorization");

        if (header != null && header.startsWith("Bearer ")) {
            // "Bearer " 이후의 토큰 부분만 추출
            String token = header.substring(7);

            // 토큰을 검증하고 클레임(사용자 정보)을 추출
            Claims claims = Jwts.parser()
                    .setSigningKey(jwtSecret)
                    .parseClaimsJws(token)
                    .getBody();

            // 클레임에서 사용자명을 추출
            String username = claims.getSubject();

            if (username != null) {
                // 추출한 사용자명을 기반으로 인증 객체 생성
                Authentication auth = new UsernamePasswordAuthenticationToken(username, null, null);
                // SecurityContextHolder에 인증 객체 저장
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }

        // 다음 필터로 전달
        filterChain.doFilter(request, response);
    }
}
  • 인코딩 필터
public class EncodingFilter implements Filter {

    private String encoding;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 필터 초기화 시 호출되는 메소드
        encoding = filterConfig.getInitParameter("encoding");
        if (encoding == null) {
            encoding = "UTF-8"; // 기본 인코딩은 UTF-8로 설정
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 필터가 실제로 수행하는 로직
        request.setCharacterEncoding(encoding);
        response.setCharacterEncoding(encoding);
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // 필터 종료 시 호출되는 메소드
    }
}

Interceptor

Interceptor란?

  • 인터셉터는 Dispatcher Servlet이 컨트롤러에 매핑되기 전, 후에 요청과 응답을 가로채서 전처리 및 후처리 작업을 수행하는 Spring Framework의 기능입니다.
  • 공통 작업 수행, 권한 확인, 로깅 등의 비즈니스 로직을 처리하여 컨트롤러에 도달하기 전에 필요한 작업을 수행합니다.

관리 영역 및 장점:

  • 인터셉터는 Spring Container의 관리를 받아 스프링의 모든 bean 객체에 접근 가능하며, Spring 기능을 사용할 수 있다는 것을 의미합니다.
  • 스프링 컨테이너의 관리를 받아서 예외 처리에 관련된 다양한 기능들을 활용할 수 있습니다.

대표적인 사용 예시

  • 로그인 인터럽트
public class AuthenticationInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 사용자의 로그인 상태를 체크하는 로직
        boolean userLoggedIn = checkUserLoggedIn(request);

        if (userLoggedIn) {
            return true; // 계속 진행
        } else {
            // 로그인이 필요한 페이지에 접근하려고 할 때 로그인 페이지로 리다이렉트
            response.sendRedirect("/login");
            return false; // 요청 중단
        }
    }

    private boolean checkUserLoggedIn(HttpServletRequest request) {
        // 실제로는 세션 또는 인증 토큰을 확인하여 사용자의 로그인 상태를 체크하는 로직
        // 여기에서는 간단히 true를 반환하도록 가정
        return true;
    }
}
  • 로깅 인터럽트
public class LoggingInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 컨트롤러 메소드 호출 전에 실행되는 로직
        // 요청 시작 시간 로깅
        log.info("Request started at: " + System.currentTimeMillis());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 컨트롤러 메소드 호출 후에 실행되는 로직
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 뷰 렌더링까지 완료된 후에 실행되는 로직
        // 요청 종료 시간 로깅
        log.info("Request completed at: " + System.currentTimeMillis());
    }
}

Leave a Comment