Java - Spring &&n SpringBoot

Spring - 서블릿 필터와 스프링 인터셉터(1) 서블릿필터, 서블릿 필터를 사용한 로그 찍기

TerianP 2022. 8. 11.
728x90

스프링 시큐리티와 서블릿 필터 && 스프링 인터셉터

최근 학원에서 파이널 프로젝트를 끝내면서 스프링 시큐리티를 다뤄서 소셜 로그인을 구현한 경험이 있다.

사실 그 당시에는 서블릿 필터도 스프링 인터셉터도 모르고 그저 '구현' 하는데 급급해서 동작 원리나 방법 등을 모르고 그냥 지나쳤다. 웃기게도 그렇게 허겁지겁 만들었던 것도 도움이 되었던건지 이번에 서블릿 필터와 스프링 인터셉터를 공부하면서 오히려 스프링 시큐리티가 어떻게 구현되는지 알 수 있었던 것 같다.

결국 스프링 시큐리티에서 동작하는 여러 기능들 - 로그인 중간에서 인터셉터해서 DB 와 비교하고, 패스워드를 확인하고, 권한을 확인하거나 로그인 여부에 따라서 페이지 이동, 권한 수준에 따른 페이지 접근 등 - 서블릿 필터와 스프링 인터셉터의 기능들을 조금 더 발전시켜서 나왔다는 것을 알게되었다.

추후 스프링 시큐리티를 이용한 소셜 로그인에 대해서도 한번 정리할 예정이나, 그전에 미리 스프링 인터셉터와 서블릿 필터를 공부하고 정리해보려한다.

 

서블릿 필터와 스프링 인터셉터

  • 모든 사용자가 들어갈 수 있는 페이지가 있는 반면 특정한 사용자만 들어갈 수 있는 페이지가 있다. 특히나 admin 권한의 유저가 들어갈 수 있는 곳은 일반 사용자가 접근해서는 안되는 페이지들이 대다수이다.
  • 이렇게 특정한 url 에 대한 요청을 걸러주고 조건이 충족되지 않은 경우 다른 곳으로 유도하는 기능이 바로 서블릿 필터와 스프링 인터셉터이다.
  • 이러한 부분을 AOP 에서 담당하도록하여 특정한 url 요청 시 AOP 메서드 실행 → 조건 확인의 로직을 타고 갈 수도 있지만 웹과 관련된 부분이기에 AOP 보다는 서블릿 필터나 스프링 인터셉터를 사용하는게 더 좋다.

서블릿 필터란?

  • 서블릿이 지원하는 수문장 ⇒ 여기서 서블릿은 디스패쳐 서블릿을 의미한다.
  • 필터의 흐름 : HTTP 요청 → WAS → (서블릿)필터 → 컨트롤러
  • 필터의 제한 : HTTP 요청 → WAS → (조건확인) → 조건 여부에 따라서 응답 변경 ⇒ 여기서의 조건은 로그인 여부, admin 여부 등등 여러가지로 상황에 맞게 설정하면 된다.
  • 필터 체인 : HTTP 요청 → WAS → 필터1 → 필터2 → 필터3 등등등 → 서블릿 → 컨트롤러 ⇒ 필터를 체인하여 다수의 필터를 추가 가능하다.

서블릿 필터 인터페이스

// HTTP 요청이 들어올 때마다 logFilter 가 먼저 실행됨
@Slf4j
public class LogFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

    }

    @Override
    public void destroy() {
    }
}
  • 필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고, 관리한다.
  • init() : 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출된다.
  • doFillter() : 고객의 요청이 올 때마다 해당 메서드가 호출된다 → 여기에 필터의 로직을 구현
  • distroy() : 필터 종료 메서드 서블릿 컨테이너가 종료될 때 호출

서블릿 인터페이스 구현

  • 여기서 중요한 것은 chain.doFilter 메서드를 반드시! 사용해서 추가 chain 이나 Servlet 을 호출해야한다는 점이다. 없으면 해당 chain만 타고 그대로 요청이 끝난다.
  • 또한 여기서 사용되는 uuid 는 특정한 사용자가 어떤 요청을 남기고 갔는지 확인하기 위한 식별값이다.
    • 즉, uuid 는 특정한 사용자가 어떤 요청을 하던 동일 uuid 를 log 에 찍음으로서 추후 log 를 확인하여 해당 사용자가 어떤 요청을 하고 나갔는지 보다 쉽게 확인할 수 있다.
// HTTP 요청이 들어올 때마다 logFilter 가 먼저 실행됨
@Slf4j
public class LogFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
log.info("log filter init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("log filter doFilter");

        // ServletRequest 는 HttpServletRequest 의 부모 클래스
        // 근데 ServletRequest 는 이전꺼라서 다운캐스팅해서 HttpServletRequest 로 만들어야한다
        HttpServletRequest req = (HttpServletRequest) request;

        String uri = req.getRequestURI(); // http 요청 uri 저장
        String uuid = UUID.randomUUID().toString(); // http 요청을 구분하기 위해서 랜덤한 uuid 생성

        try {
log.info("REQUEST [{}][{}]", uuid, uri);

            // chain 을 사용해서 다음 필터를 불러와야한다.
            // 만약 다음 필터가 존재한다면 해당 필터가 실행되고 아니라면 Servlet 이 호출된다.
            // 반드시!! 필수!!! => 없으면 추가 chain 이던 Servlet 이던 모두 호출이 안된다
            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e;
        }finally {
log.info("RESPONSE [{}][{}]", uuid, uri);
        }
    }

    @Override
    public void destroy() {
log.info("log filter destory");
    }
}

Servlet Filter Bean 등록

  • 우리가 만든 logFilter 가 잘 실행되도록 Spring Bean 으로 등록한다.
// Servlet Filter 를 사용하기 위한 Bean
@Bean
public FilterRegistrationBean logFilter(){
    FilterRegistrationBean<Filter> filterBean = new FilterRegistrationBean<Filter>();
    filterBean.setFilter(new LogFilter());

    // 필터 순서 => 필터 체인 시 사용되는 순서
    filterBean.setOrder(1);

    // 필수 적용 URL => 필터 적용 시 사용되는 url : /* 라면 모든 url 에 적용됨
    filterBean.addUrlPatterns("/*");

    return filterBean;
}


필터 실행 확인

uuid , 요청 uri 및 request, response 모두 잘 남는다

 

댓글