Spring Framework/끄적끄적

[Spring] 필터(Filter)란 무엇인가?

포포015 2021. 6. 23. 16:02

필터(Filter)란 무엇일까?

HTTP 요청과 응답을 변경할수 있는 재사용 가능한 코드.(서블릿 2.3규약에 새롭게 추가됨)

필터는 객체의 형태로 존재하며 클라이언트로부터 오는 요청(Request)과 최종자원(서블릿/JSP)사이에 위치하며,

클라이언트의 요청정보를 알맞게 변경할수 있으며, 또한 필터는 최종 자원과 클라이언트로 가는 응답(Response) 사이에 위치하여 최종 자원의 요청 결과를 알맞게 변경할수 있다.

 

필터의 기본 구조

그림으로 보면 자원이 받게되는 요청 정보는 클라이언트와 자원사이에 존재하는 필터에 의해 변경된 요청정보가 되며,

클라이언트가 보게되는 응답정보는 클라이언트와 자원사이에 존재하는 필터에 의해 변경된 응답정보가 된다.

필터는 클라이언트와 자원사이에 1개가 존재하는 경우가 보통이지만,

여러개의 필터가 모여서 하나의 체인(Chain)을 형성할수 있다.

 

여러개의 필터가 모여서 하나의 체인을 형성할때 첫번째 필터가 변경하는 요청정보는 클라이언트의 요청정보가 되지만,

체인의 두번째 필터가 변경하는 요청정보는 첫번째 필터를 통해서 변경된 요청정보가된다.

즉 요청정보는 변경에 변경에 변경을 거듭한다. 

응답정보의 경우도 요청정보와 비슷한 과정을 거치지만 차이점은 필터의 적용순서가 요청때와는 반대다.

 

또한 필터는 요청/응답 정보를 변경하는 역할뿐 아니라, 흐름을 변경하는 역활도 할수 있다.

즉 필터는 클라이언트의 요청을 필터체인의 다음단계에 보내

는것이 아니라, 다른자원의 결과를 클라이언트에 전송 가능

필터의 이러한 기능을 사용자 인증이나, 권한 체크와 같은곳에서 사용한다

 

[필터 관련 인터페이스 및 클래스]

필터를 구현하는데 핵심적인 역할을 하는 인터페이스/클래스가 3개 있다.

1) javax.servlet.Filter 인터페이스

2) javax.servlet.ServletRequestWrapper 클래스

3) javax.servlet.ServletResponseWrapper 클래스

 

이중 Filter 인터페이스는 클라이언트와 최종 자원사이에 위치하는 필터를 나타내는 객체가 구현해야 하는 인터페이스다.

(Filter를 사용하려면 제일 처음에 상속받아 구현)

그리고, ServletRequestWrapper 클래스와 ServletResponseWrapper 클래스는 필터가 요청을 변경한결과 또는 응답을

변경한 결과를 저장할 래퍼 클래스에 나타낸다. (개발자는 두클래스를 알맞게 상속하여 요청/응답 정보를 변경하면됨)

1)Filter 인터페이스

Filter 인터페이스 부터 살펴보겠다. Filter 인터페이스에서는 다음과 같은 메소드가 선언되어 있다.

 

  • public void init(FilterConfig filterConfig)
  • public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  • public void destroy()

init() -

필터를 웹 컨테이너에 생성한후 초기화 할때 호출한다.

doFilter() -

체인을 따라 다음에 존재하는 필터로 이동한다. 체인의 가장 마지막에는 클라이언트가 요청한 최종자원이 위치한다 

destroy() - 

필터가 웹 컨테이너에 삭제될때 호출된다

 

위의 메소드에서 필터의 역할을 하는 메소드가 바로 doFilter() 메소드이다.

서블릿 컨테이너는 특정한 자원을 요청했을때 그 자원사이에 필터가 존재할경우 그 필터 객체의

doFilter()메소드를 호출하며, 바로 이시점부터 필터가 작용하기 시작한다. 다음은 전형적인 필터의 구현방법을 보여준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 public class FirstFilter implements javax.servlet.Filter {
  
     public void init(FilterConfig filterConfig) throws ServletException {
        // 필터 초기화 작업
     }
     
     public void doFilter(ServletRequest request,
                          ServletResponse response,
                          FilterChain chain)
                          throws IOException, ServletException {
        // 1. request 파리미터를 이용하여 요청의 필터 작업 수행
        // 2. 체인의 다음 필터 처리
        chain.doFilter(request, response);        // 3. response를 이용하여 응답의 필터링 작업 수행
     }
     
     public void destroy() {
        // 주로 필터가 사용한 자원을 반납
     }
  }
cs

위의 코드에서 Filter 인터페이스의 doFilter()메소드는 javax.servlet.Servlet 인터페이스의 service()메소드와 비슷한구조다.

즉, 만약 클라이언트의 자원 요청이 필터를 거치는경우, 클라이언트의 요청이 있을때마다 doFilter()메소드가 호출되며

doFilter() 메소드는 서블릿과 마찬가지로 각각의 요청에 대해서 알맞은 작업 처리를 하게 된다.

 

위의 코드를 보면 doFilter() 메소드는 세번째 파라미터로 FilterChain 객체를 전달받는것을 알수있다.

이는 클라이언트가 요청한 자원에 이르기까지 클라이언트의 요청이 거쳐가게 되는 필터체인을 나타낸다.

FilterChain을 사용함으로써 필터는 체인에 있는 다음필터에 변경한 요청과 응답을 건내줄수 있게된다.

 

그리고 위의 코드를 보고 알아야 하는것은, 요청을 필터링한 필터 객체가 또다시 응답을 필터링한다는점이다.

doFilter()메소드를 보면 1,2,3 이라는 숫자를 사용하여 doFilter()메소드 내에서 이루어지는 작업의 순서를 표시하였는데,

순서를 다시 정리해보면 아래와 같다.

 

1) Requset 파라미터를 이용하여 클라이언트의 요청을 필터링

  (1단계 에서는 RequestWrapper 클래스를 사용하여 클라이언트의 요청을 변경한다.)

2) chain.doFilter() 메소드 호출

  (2단계 에서는 요청의 필터링 결과를 다음 필터에 전달한다.)

3) response 파라미터를 사용하여 클라이언트로 가는 응답 필터링

  (3단계 에서는 체인을 통해 전달된 응답데이터를 변경하여 그결과를 클라이언트에 전송)

 

1단계와 3단계 사이에 다음필터로 이동하기때문에 요청의 필터 순서와 응답필터 순서는 맨처음에 그림과같이 반대가됨

 

[필터의 설정]

필터를 사용하기 위해서는 어떤 필터가 어떤자원에 대해서 적용된다는것을 서블릿/JSP 컨테이너에 알려줘야한다.

서블릿 규약은 웹어플리케이션과 관련된 설정은 웹 어플리케이션 디렉토리의 /WEB-INF디렉토리에 존재하는

web.xml 파일을 통해서 하도록 하고있으며, 필터 역시 web.xml 파일을 통해 설정하도록 되어있다.

 

web.xml파일에서 필터를 설정하기 위해서는 다음과 같이 <filter> 태그와 <filter-mapping>태그를 사용하면된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  <web-app>
     
     <filter>
        <filter-name>HighlightFilter</filter-name>
        <filter-class>javacan.filter.HighlightFilter</filter-class>
        <init-param>
           <param-name>paramName</param-name>
           <param-value>value</param-value>
        </init-param>
     </filter>
     
     <filter-mapping>
        <filter-name>HighlightFilter</filter-name>
        <url-pattern>*.txt</url-pattern>
     </filter-mapping>
     
  </web-app>
cs
  • <filter>태그는 웹 어플리케이션에서 사용될 필터를 지정하는 역활
  • <filter-mapping>태그는 특정 자원에 대해 어떤 필터를 사용할지 지정 (위의 예제경우 클라이언트가 txt 확장자를 갖는 자원을 요청할경우 HighlightFilter가 사용되도록 지정하고있음)
  • <init-param>태그는 필터가 초기화 될때, 즉필터의 init()메소드가 호출될때 전달되는 파라미터 값이다. 이는 서블릿의 초기화 파라미터와 비슷한 역할을 하며, 주로 필터를 사용하기전에 초기화해야하는 객체나 자원을 할당할때 필요한 정보를 제공하기 위해 사용됨.
  • <url-pattern> 태그는 클라이언트가 요청한 특정 URL에 대해 필터링을 할때 사용한다
  • '/'로 시작하고 '/*'로 끝나는 url-pattern은 경로 매핑을 위해서 사용된다.
  • '*.'로 시작하는 url-pattern은 확장자에 대한 매핑을 할 때 사용된다.
  • 나머지 다른 문자열을 정확한 매핑을 위해서 사용된다.
1
2
3
4
<filter-mapping>
        <filter-name>AuthCheckFilter</filter-name>
        <url-pattern>/pds/*</url-pattern>
     </filter-mapping>
cs

이 경우 클라이언트가 /pds/a.zip 을 요청하든 /pds/b.zip 을 요청하는지에 상관없이 AuthCheckFilter가 필터로 사용될 것이다.

<url-pattern> 태그를 사용하지 않고 대신 <servlet-name> 태그를 사용함으로써 특정 서블릿에 대한 요청에 대해서 필터를 적용할 수도 있다. 예를 들면 다음과 같이 이름이 FileDownload인 서블릿에 대해서 AuthCheckFilter를 필터로 사용하도록 할 수 있다.

1
2
3
4
5
6
7
8
9
     <filter-mapping>
        <filter-name>AuthCheckFilter</filter-name>
        <servlet-name>FileDownload</servlet-name>
     </filter-mapping>
     
     <servlet>
        <servlet-name>FileDownload</servlet-name>
        ...
     </servlet>
cs

 

[래퍼 클래스]

필터가 필터로서의 제기능을 하기위해서는 클라이언트의 요청을 변경하고,

또한 클라이언트로 가는 응답을 변경할수 있어야 할것이다.

이러한 변경을 할수 있도록 해주는것이 바로 SerlvetRequestWrapper와 ServletResponseWrapper이다.

서블릿 요청/응답 래퍼 클래스를 이용함으로써 클라이언트의 요청정보를 변경하여, 최종자원인

서블릿/JSP/HTML/기타자원에 전달할수 있고,또한 최종자원으로부터의 응답결과를 변경하여 새로운 응답정보를 

클라이언트에 보낼수 있게된다.

 

서블릿 요청/응답 래퍼 클래스로서의 역할을 수행하기 위해서는 javax.servlet 패키지에 정의되어 있는

ServletRequestWrapper 클래스와 ServletResponseWrapper 클래스를 상속받으면된다.

하지만 대부분의 경우 HTTP 프로토콜에 대한 요청/응답을 필터링 하기 때문에 이 두클래스를 상속받아

알맞게 구현한 HttpServletRequestWrapper 클래스와 HttpServletResponseWrapper 클래스를 상속받는 경우가 대부분일 것이다.

HttpServletRequestWrapper 클래스와 HttpServletResponseWrapper 클래스는 모두 javax.servlet.http 패키지에 정의되어 있으며, 이 두 클래스는 각각 HttpServletRequest 인터페이스와 HttpServletResponse 인터페이스에 정의되어 있는 모든 메소드를 이미 구현해 놓고 있다. 필터를 통해서 변경하고 싶은 정보가 있을 경우 그 정보를 추출하는 메소드를 알맞게 오버라이딩하여 필터의 doFilter() 메소드에 넘겨주기만 하면 된다. 예를 들어, 클라이언트가 전송한 "company" 파리머터의 값을 무조건 "JavaCan.com"으로 변경하는 요청 래퍼 클래스는 다음과 같이 HttpServletRequestWrapper 클래스를 상속받은 후에 getParameter() 메소드를 알맞게 구현하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  package javacan.filter;
  
  import javax.servlet.http.*;
  
  public class ParameterWrapper extends HttpServletRequestWrapper {
     
     public ParameterWrapper(HttpServletRequest wrapper) {
        super(wrapper);
     }
     
     public String getParameter(String name) {
        if ( name.equals("company") ) {
           return "JavaCan.com";
        } else {
           return super.getParameter(name);
        }
     }
  }
cs

오버라이딩한 getParameter() 메소드를 살펴보면 값을 구하고자 하는 파라미터의 이름이 "company"일 경우 "JavaCan.com"을 리턴하고 그렇지 않을 경우에는 상위 클래스(즉, HttpServletRequestWrapper 클래스)의 getParameter() 메소드를 호출하는 것을 알 수 있다.

이렇게 작성한 래퍼 클래스는 필터 체인을 통해서 최종 자원까지 전달되어야 그 효과가 있을 것이다. 즉, 최종 자원인 서블릿/JSP에서 request.getParameter("company")를 호출했을 때 ParameterWrapper 클래스의 getParameter() 메소드가 사용되기 위해서는 ParameterWrapper 객체가 HttpServletRequest 객체를 대체해야 하는데, 이는 Filter 인터페이의 doFilter() 내에서 ParameterWrapper 객체를 생성한 후 파라미터로 전달받은 FilterChain의 doFilter() 메소드를 호출함으로써 가능하다. 좀 복잡하게 느껴질지도 모르겠으나 이를 코드로 구현해보면 다음과 같이 간단한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 package javacan.filter;
  
  import javax.servlet.*;
  import javax.servlet.http.*;
  
  public class ParameterFilter implements Filter {
     
     private FilterConfig filterConfig;
     
     public ParameterFilter() {
     }
     
     public void init(FilterConfig filterConfig) {
        this.filterConfig = filterConfig;
     }
     
     public void destroy() {
        filterConfig = null;
     }
     
     public void doFilter(ServletRequest request,
                          ServletResponse response,
                          FilterChain chain)
                          throws java.io.IOException, ServletException {
        // 요청 래퍼 객체 생성
        HttpServletRequestWrapper requestWrapper = 
                     new ParameterWrapper((HttpServletRequest)request);
        // 체인의 다음 필터에 요청 래퍼 객체 전달
        chain.doFilter(requestWrapper, response);     }
  }
cs

응답 래퍼 클래스 역시 요청 래퍼 클래스와 비슷한 방법으로 구현된다.

 

 

출처 : https://javacan.tistory.com/entry/58