본문 바로가기

Study/개발일지

[백엔드스터디WIL]7주차 학습일지

Filter 란?

 

사실 필터는 스프링의 독자적인 기능이 아닌 자바 서블릿에서 제공하는 기능입니다. 스프링 프레임워크에서 필터로 인증 등 다양한 작업을 하는 데 사용하니 스프링 프레임워크에서의 필터에 대해 기록 해 보고자 합니다.

 

(출처:https://justforchangesake.wordpress.com/2014/05/07/spring-mvc-request-life-cycle/)

아마 스프링 필터와 연관지어 검색을 하면 많이 보는 그림 중 하나 일 것입니다.

 

위 그림은 스프링 프레임워크에서 요청에 대한 라이프 사이클을 나타낸 그림입니다.

 

스프링 프레임워크는 들어온 요청이 DispatcherServlet에 의해 컨트롤러에 매핑됩니다.

 

Filter는 요청이 DispatcherServlet에 의해 다뤄지기 전, 후에 동작합니다.

 

또한 Filter는 FilterChain(필터 체인)을 통해 여러 필터가 연쇄적으로 동작하게 할 수 있습니다.

 

Filter는 어디에 쓰이나?

필터는 주로 요청에 대한 인증, 권한 체크 등을 하는데에 쓰입니다. 구체적으로 들어온 요청이 DispatcherServlet에 전달되기 전에 헤더를 검사해 인증 토큰이 있는지 없는지, 올바른지 올바르지 않은지 등을 검사할 수 있을 것입니다.

 

저는 개인 게시판 프로젝트에서 SpringSecurity와 Jwt를 이용한 인증을 구현하는 데에 있어 Filter를 사용한 경험이 있습니다.

코드 : 보러 가기

 

97e57e/springboot-garden-board-api

spring boot rest api practice. Contribute to 97e57e/springboot-garden-board-api development by creating an account on GitHub.

github.com

 

 

Filter, 어떻게 사용 하나?

그럼 이제 스프링에서 필터를 어떻게 사용하는지 알아보겠습니다.

 

먼저 필터 클래스를 만들어야 하는데 필터 클래스는 servlet의 Filter 인터페이스를 구현하여 만들 수 있습니다.

public class FirstFilter implements Filter {}

 

필터 인터페이스는 3가지 메소드를 갖고 있는데 각각 다음과 같습니다.

  1. init() : 필터 가 생성될 때 수행되는 메소드
  2. doFilter() : Request, Response가 필터를 거칠 때 수행되는 메소드
  3. destroy() : 필터가 소멸될 때 수행되는 메소드

위의 세 가지 메소드를 아래와 같이 구현하면 필터가 됩니다.

@Slf4j
public class FirstFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("FirstFilter가 생성 됩니다.");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("==========First 필터 시작!==========");
        filterChain.doFilter(servletRequest, servletResponse);
        log.info("==========First 필터 종료!==========");
    }

    @Override
    public void destroy() {
        log.info("FirstFilter가 사라집니다.");
    }
}

 

필터 클래스를 만들면 이 필터를 Spring Bean으로 등록해야 합니다.

@Configuration
public class Config{

    @Bean
    public FilterRegistrationBean firstFilterRegister() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(new FirstFilter());
        return registrationBean;
    }
}

 

필터를 빈으로 등록하기 위해 스프링 설정에 FilterRegistrationBean을 이용해 직접 만든 필터를 등록할 수 있습니다.

 

우리는 방금 직접 만든 FirstFilter를 FilterRegistrationBean을 이용해 스프링의 필터로 등록했습니다.

 

등록된 필터가 어떻게 동작하는지 보기 위해 임시 컨트롤러를 하나 만들어 보겠습니다.

@Slf4j
@RestController
public class MyController {
    @GetMapping("/")
    public String hello() {
        log.info("Hello Garden!!!");
        return "Hello";
    }
}

 

위에 만든 컨트롤러의 hello 메소드가 수행되도록 요청을 한번 보내 보면!

 

스프링이 시작하면서 FirstFilter의 init 메소드가 수행된 것을 볼 수 있고 요청이 들어왔을 때, 필터 - 컨트롤러 - 필터로 예상한 대로 결과가 나왔음을 볼 수 있습니다.

 

그럼 두 개 이상의 필터가 있으면 어떻게 될지 한번 보겠습니다. FirstFilter와 비슷하게 SecondFilter 클래스를 만들었습니다.

@Slf4j
public class SecondFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("SecondFilter가 생성 됩니다.");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("==========Second 필터 시작!==========");
        filterChain.doFilter(servletRequest, servletResponse);
        log.info("==========Second 필터 종료!==========");
    }

    @Override
    public void destroy() {
        log.info("SecondFilter가 사라집니다.");
    }
}

 

이 SecondFilter 또한 빈으로 등록을 해줘야 합니다.

@Configuration
public class Config{

    @Bean
    public FilterRegistrationBean firstFilterRegister() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(new FirstFilter());
        registrationBean.setOrder(1);
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean secondFilterRegister() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(new SecondFilter());
        registrationBean.setOrder(2);
        return registrationBean;
    }
}

 

SecondFilet도 FirstFilter와 같은 방법을 통해 등록해주면 됩니다. 한 가지 추가된 것은 여러 개의 필터가 있을 때, 필터의 동작 순서를 FilterRegistrationBean의 setOrder() 메소드를 통해 결정할 수 있습니다.

 

FirstFilter가 처음으로, SecondFilter가 두 번째로 수행되게 설정을 하고 요청을 보내보겠습니다.

 

FirstFilter - SecondFilter - Controller - SecondFilter - FirstFilter 순으로 동작한 것을 볼 수 있습니다. 

 

이는 아까 위에서 보았던 그림을 다시 한번 보면 어떻게 동작하는지 확실하게 이해가 갈 것입니다.

 

 

LogoutFilter

로그아웃을 하기 위해서는 logout request를 받았을 때, server의 세션을 무효화하고 쿠키 정보, 인증토큰과 인증토큰이 저장된 Security Context의 객체를 삭제해줘야 합니다.

동작 원리


1. AntPathRequestMatcher를 통해 logout을 처리하는 url이 들어왔는지 확인을 합니다. 만약 해당 url이 아닐 경우 다음 Filter로 이동을 합니다.
2. Authentication객체를 Security Context에서 찾고 해당 객체를 SecurityContextLogoutHander로 넘겨줍니다.
3. 기본적인 LogoutHandler는 4개가 존재하는데 그 중 SecurityContextLogoutHander는 세션 무효화, 쿠키 삭제, SecurityContextHolder내용 삭제 등의 작업을 해줍니다.
4. LogoutFilter의 작업이 끝나면 SimpleUrlLogoutSuccessHander가 동작을 하게 됩니다.

LogoutFilter사용하기

Spring Security의 Logout기능을 사용하기 위해서는 http.logout()을 사용하면 로그아웃 기능이 작동합니다.

세부 API는 다음과 같습니다.

    protected void configure(HttpSecurity http) throws Exception {
        http.logout() // 로그아웃 처리
                .logoutUrl("/logout") // 로그아웃 처리 URL
	        .logoutSuccessUrl("/login") // 로그아웃 성공 후 이동페이지
                .deleteCookies("JSESSIONID", "remember - me") // 로그아웃 후 해당 쿠키 삭제
                .addLogoutHandler(logoutHandler()) // 로그아웃 핸들러
                .logoutSuccessHandler(logoutSuccessHandler()) // 로그아웃 성공 후 핸들러
    }
  • logoutUrl("/logout"): 로그인 처리를 할 URL을 입력해주면 된다. (이때 spring security에서는 원칙적으로 logut을 실행할 때는 post mapping을 한다.)
  • logoutSuccessUrl("/login"): 로그아웃이 성공하였을 때 이동할 페이지의 경로를 입력해준다.
  • deleteCookies("JSESSIONID", "remember - me"): 로그아웃 할 때 서버에서 만든 쿠키를 삭제하고 싶을 때 사용한다. 값은 삭제하고 싶은 쿠키 명을 입력해주면 된다.
  • addLogoutHandler(logoutHandler()): 로그아웃을 하였을 때 Spring Security가 기본적으로 구현한 로그아웃 구현체는 세션을 무효화시키고 인증 토큰을 삭제하는 등의 동작을 한다. 그 외에 추가적으로 개발자가 구현하고 싶은 내용이 있을 경우 새로운 logoutHandler를 만들고 해당 api를 통해 사용할 수 있습니다.
  • logoutSuccessHandler(logoutSuccessHandler()): 로그아웃이 성공했을 때 logoutSuccessHandler를 호출하는 api입니다. 파라미터로는 logoutSuccessHandler 인터페이스를 구현한 것을 넣으면 됩니다.

logiytSuccessUrl()과 logoutSuccessHandler()의 차이

  • logiytSuccessUrl()는 단순히 로그아웃을 성공하였을 때 해당 url로 이동하는 것이 전부라면 logoutSuccessHandler()는 구체적인 여러 내용들을 설정할 수 있습니다.
728x90