동아리 웹 사이트를 만드는 프로젝트를 진행하고 있는데, 인증 관련 서비스 개발을 할 때, JWT 토큰을 확인하는 CustomFilter를 만들어 사용했었다. 솔직히 처음에 쓸 때는 막연하게 기능 개발이 우선이어서 인터넷에서 여기저기 글을 읽으며 코드를 보고 동작이 되게 구현한거라 정확한 원리는 잘 몰랐다.
그런데, 최근에 문제가 하나 발생했다.
스프링 시큐리티 설정을 하는 SecurityConfig 클래스에서@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // http .requestMatchers("/photo-post/**").permitAll() // http .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class) .addFilterBefore(customAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class); // return http.build(); }
뭐 대충 이런 코드에서 특정 요청 url에 대해 permitAll()을 해주면 필터를 거치지 않을거라 생각했는데 그렇지 않았다.
이 문제를 외면하고 있다가 이제는 해결해야할 것 같아서 찾아보니, 필터에 대한 지식이 부족해서 발생한 문제였다.
필터가 뭘까.. 필터.. 정수기도 있고 에어컨에도 있다. 정수기에서는 모든 물이 이 필터를 거쳐서 깨끗한 물이 된다. 에어컨에서도 모든 동기가 이 필터를 거쳐서 시원하고 깨끗한 공기가 된다.
서블릿 필터도 비슷하다. 클라이언트와 스프링 디스패쳐 서블릿을 오고 가는 모든 요청과 응답은 이 필터를 거침으로써 어떤 일이 일어나게 된다. 여기서 request와 response가 정수기의 물과 같고, servlet filter가 정수기의 필터와 같다.
클라이언트로부터 전송된 요청이 디스패쳐 서블릿에 도달하기 전, 그리고 디스패쳐 서블릿에서 전송된 응답이 클라이언트에 도달하기 전 모든 요청에 대해 부가 작업을 할 수 있도록 하는 인터페이스이다. 필터는 서블릿 컨테이너에 의해 관리된다.
주로 인증, 권한 체크, 공통 로깅 처리 등에서 사용된다. Http 요청 헤더를 검사해 인증 토큰이 있는지 없는지, 토큰이 정상적인지 비정상적인지 검사하는 로직은 모든 요청하다 수행되어야 할 것이다.
주로 이렇게 모든 요청과 응답에 수행되어야 하는 로직을 필터에서 수행한다.
- init() : 필터를 생성하고 초기화한다. 서블릿 컨테이너는 애플리케이션 실행 시 한번 매서드를 호출함으로써 필터를 생성한다.
- doFilter() : ServletRequest, ServletResponse가 필터를 거칠 때 수행된다. FilterChain을 파라미터로 갖고 있는데, filterChain.doFilter(request, response); 를 통해 필터체인의 다음 필터로 ServletRequest, ServletResponse가 전달된다. 따라서, filterChain.doFilter() 전/후에 필요한 로직을 작성하여 의도한 결과를 얻을 수 있다.
- destroy() : 필터를 소멸시킨다.
- Filter를 상속받아 스프링 설정 정보를 가져올 수 있게 확장된 추상 클래스이다.
- 해당 추상 클래스를 구현한 클래스는 스프링 빈으로 등록되고 자동으로 필터 체인에 추가된다.
- GenericFilterBean을 상속받아 Http 요청당 한번만 실행되는 필터이다.
- 보통 인증, 인가 시, 사용한다.
- Http 요청이나 응답을 수정해야할 때, 요청당 한번만 필터를 실행하여 중복 수정을 방지할 수 있다.
public class CustomSecurityFilter extends OncePerRequestFilter { // @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // doFilterInternal() 메서드에서 필터의 실제 동작을 정의 // 요청 및 응답을 수정하거나 처리 // 필터 체인을 계속 진행 filterChain.doFilter(request, response); } // @Override public void doFilter( ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { // doFilter() 메서드는 실제 동작을 doFilterInternal() 메서드로 위임 super.doFilter(request, response, filterChain); } }