본문 바로가기
스프링/JPA

[JSP] 필터(Filter)란?

by 책 읽는 개발자_테드 2021. 10. 5.
반응형

학습 목표

· 필터란?

· 필터의 구현

· 필터 설정하기: web.xml 이용

· 필터 설정하기: @WebFilter 애노테이션 이용

· 필터의 응용

   - 로그인 검사 필터

   - TODO: XSL/T 필터

   - TODO: 캐릭터 인코딩 필터


필터란?

· HTTP 요청과 응답을 변경할 수 있는 재사용 가능한 클래스

· 특징:

   - 객체 형태로 존재

   - 클라이언트에서 오는 요청(request) 최종 자원 사이에 위치하여, 클라이언트의 요청 정보를 알맞게 변경 가능

    - 최종 자원과 클라이언트로가는 응답(response) 사이에 위치하여, 최종 자원의 요청 결과를 알맞게 변경 가능

https://youmekko.github.io/2018/04/26/2018-04-26-Filter/

 

· 클라이언트와 자원 사이에 여러 개의 필터가 모여 하나의 필터 체인을 형성할 수 있음

· 필터는 정보 뿐만 아니라 흐름도 변경할 수 있음

   - 즉, 클라이언트의 요청을 필터 체인의 다음 단계(클라이언트가 요청한 자원)에 보내는 것이 아니라

      다른 자원의 결과를 클라이언트에 전송할 수 있음

   - 이러한 기능은 사용자 인증이나 권한 검사 등에 용이하게 사용할 수 있음

 

필터의 구현

· 필터 구현을 위한 핵십 타입 세 가지 - 개발자는 세 타입을 이용해서 필터를 구현하고, 요청과 응답 정보를 변경하는 기능을 구현 

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

   2. javax.servlet.ServletRequestWrapper 클래스 : 필터가 요청을 변경한 결과를 저장하는 래퍼

   3. javax.servlet.ServletResponseWrapper 클래스: 필터가 응답을 변경하기 사용하는 래퍼

· 필터 클래스를 컴파일하려면 서블릿 API가 포함된 jar 파일을 클래스패스에 추가해야함

 

Filter 인터페이스

· 필터 기능을 제공할 클래스는 Filter 인터페이스를 알맞게 구현해주어야함

· Filter 인터페이스에 선언된 메서드:

public void init(FilterConfig filterConfig) throws ServletException
// 필터를 초기화할 때 호출
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException
// 필터 기능을 수행, chain을 이용해서 체인의 다음 필터로 쿼리 전달
public void destory()
// 필터가 웹 컨테이너에서 삭제될 때 호출

 

· 서블릿 컨테이너는 사용자가 특정한 자원을  요청했을 때, 그 자원 사이에 필터가 존재하는 경우 필터 객체의 doFilter() 메서드를 호출함

   - 이 시점부터 필터를 적용하기 시작

 

▶ 예시 - 필터의 구현 방법

public class FirstFilter implements Filter {
    
    public void init(FilterConfig filterConfig) throws ServletException {
        // 필터 초기화 작업
    }
    
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException) throws IOException, ServletException {
        // 1. request 파라미터를 이용하여 요청의 필터 작업 수행
              RequestWrapper 클래스를 사용해서 클라이언트 요청을 변경 가능
        ...
        // 2. 체인의 다음 필터 처리
              요청의 필터링 결과를 다음 필터에 전달
        chain.doFilter(request, response);
        
        // 3. response를 이용하여 응답의 필터링 작업 수행
			  체인을 통해서 전달된 응답 데이터를 변경하여 그 결과를 클라이언트에 전송
		...
    }
    
    public void destroy(){
        // 주로 필터가 사용한 자원을 반납
    }
}

· doFilter() 메서드는 요청이 있을 때마다 매번 실행

· doFilter 메서드의 세 번째 파라미터 FilterChain 객체는 클라이언트가 요청한 자원에 이르기까지 클라이언트의 요청이 거쳐 가게 되는 필터 체인을 의미

   - FilterChain을 사용해서 필터는 체인에 있는 다음 필터에 변경한 요청과 응답을 전달 가능

· 요청을 필터링 한 필터 객체가 또다시 응답을 필터링

   - 1단계와 3단계 사이에 다음 필터로 이동하기 때문에 요청의 필터 적용순서와 응답의 필터 적용 순서는 반대

· init() 메서드에 전달되는 FilterConfig: 필터의 초기화 파라미터를 읽어올 때 사용

· FilterConfig가 제공하는 메서드

메서드 리턴 타입 설명
getFilterName() String · 설정 파일에서 <filter-name>에 지정한 필터의 이름을 리턴 
getInitParameter(String name) String · 설정 파일의 <init-param>에서 지정한 초기화 파라미터 값을 읽어옴
· 존재하지 않는 경우 null 리턴
getInitParameterNames() Enumeration<String> · 초기화 파라미터의 이름 목록을 구함
getServletContext() ServletContext · 서블릿 컨텍스트 객체를 구함

 

필터 설정하기: web.xml 이용

· web.xml 파일에 필터를 설정하려면, <filter> 태그 <filter-mapping> 태그를 사용

<?xml version="1.0" encoding="utf-8"?>

<web-app ...>
  
  <filter>
    <filter-name>FilterName</filter-name>
    <filter-class>javacan.filter.FileClass</filter-class>
    <init-param>
      <param-name>paramName</param-name>
      <param-value>value</param-value>
    </init-pram>
  </filter>
  
  <filter-mapping>
    <filter-name>FilterName</filter-name>
    <url-pattern>*.jsp</url.patter>
  </filter-mapping>
  
  ...
  
</web-app>

· <filter> 태그: 웹 어플리케이션에서 사용할 필터 지정

· <filter-mapping> 태그: 특정 자원에 대해 어떤 필터를 사용할지 지정

   - 위 예제는 클라이언트가 jsp 확장자를 갖는 경로를 요청한 경우 FilterName 필터를 적용하도록 설정

· <init-param> 태그: 필터를 초기화할 때, 즉 필터의 init() 메서드를 호출할 때 전달할 파라미터를 설정

   - 주로 필터를 사용하기 전에 초기화 작업에 필요한 정보를 제공하기 위해 사용

· <url-pattern> 태그: 클라이언트가 요청한 특정 URI에 대해서 필터링 할 때 사용

   - 서블릿에서 <url-pattern>과 동일한 규칙을 갖음 https://scshim.tistory.com/397

더보기

· URL 패턴이 서블릿을 매핑하는 규칙:
   - '/'로 시작하고 '/*'로 끝나는 url-pattern은 경로 매핑을 위해서 사용
   - '*.'로 시작하는 url-pattern은 확장자에 대한 매핑을 할 때 사용
   - 오직 '/'만 포함하는 경우 어플리케이션의 기본 서블릿으로 매핑
   - 이 규칙 외, 나머지 다른 문자열은 정확한 매핑을 위해 사용

 

· <url-pattern> 태그 대신 <servlet-name> 태그를 사용하면, 특정 서블릿에 대한 요청에 대해서 필터를 적용함

▶ 예시 - <servlet-name> 태그를 사용해서 FileDownload인 서블릿에 대해 AuthCheckFilter를 필터로 사용

<filter-mapping>
  <filter-name>AuthCheckFilter</filter-name>
  <servlet-name>FileDownLoad</servlet-name>
</filter-mapping>

<servlet>
  <servlet-name>FileDownLoad</servlet-name>
  ...
</servlet>

 

· <dispatcher> 태그를 사용하면 필터가 적용되는 시점을 설정 가능

   - <dispatcher> 태그는 <filter-mapping> 태그의 자식 태그로 사용됨

<filter-mapping>
  <filter-name>AuthCheckFilter</filter-name>
  <servlet-name>FileDownLoad</servlet-name>
  <dispatcher>INCLUDE</dispatcher>
</filter-mapping>

 

· <dispatcher> 태그가 가질 수 있는 값:

   - REQUEST: 클라이언트의 요청인 경우 필터를 적용 (기본값)

   - FORWARD: forward()를 통해서 제어 흐름을 이동하는 경우에 필터 적용

   - INCLUDE: include()를 통해서 포함되는 경우에 필터 적용

 

▶ 예시 - <dispatcher> 태그 사용하기

<filter-mapping>
  <filter-name>AuthCheckFilter</filter-name>
  <url-pattern>/pds/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
</filter-mapping>

<filter-mapping>
  <filter-name>IPCheckFilter</filter-name>
  <url-pattern>/pds/data/*</url-pattern>
  <dispatcher>INCLUDE</dispathcer>
</filter-mapping>

· 웹 브라우저 /pds/data/download.jsp를 요청하면,

  두 개의 필터 매핑 모두 <url-pattern>에 따라 이 URL을 처리할 수 있지만,

  두 번째 필터 매핑은 <dispatcher>의 값이 INCLUDE이므로 웹 브라우저의 요청에 대해서는 필터 적용 x

 

· 아래와 같이 <jsp:include> 코드를 사용했을 경우,

  /pds/data/util.jsp는 두 개의 필터 모두에 해당되지만,

   첫 번째 필터는 웹 브라우저의 요청에 대해서만 적용되기 때문에 사용되지 않고,

   <dispatcher>의 값이 INCLUDE인 두 번째 필터만 사용

<jsp:include page="/pds/data/util.jsp" flush="false" />

 

· 웹 브라우저의 요청이 동시에 여러 개의 필터 매핑에 적용되는 경우,

  web.xml 파일에 표시한 순서대로  필터를 적용

<filter-mapping>
  <filter-name>AuthCheckFilter</filter-name>
  <url-pattern>/pds/*</url-pattern>
</filter-mapping>

<filter-mapping>
  <filter-name>IPCheckFilter</filter-name>
  <url-pattern>/pds/data/*</url-pattern>
</filter-mapping>

<filter-mapping>
  <filter-name>CompressFilter</filter-name>
  <url-patter>/pds/data/*</url-patter>
</filter-mapping>

· 위와 같은 경우 웹 브라우저가 /pds/data/a.jsp를 요청하면 필터는

  AuthCheckFilter->IPCheckFilter->CompressFilter 순서대로 실행

 

· 하나의 <filter-mapping>에서 한 개 이상의 <url-pattern> 태그와 <servle-name> 태그를 설정할 수 있음

<filter-mapping>
  <filter-name>IPCheckFilter</filter-name>
  <url-pattern>/pds/data/*</url-pattern>
  <url-pattern>/webinterface/*</url-pattern>
  <servlet-name>DownloadServlet</servlet-name>
</filter-mapping>

 

필터 설정하기: @WebFilter 애노테이션 이용

· Filter 클래스가 @WebFilter 애노테이션을 가지면 자동으로 필터로 등록됨

 

▶ 예시 - @WebFilter 애노테이션을 사용하는 방법

import javax.servlet.annotation.WebFilter;

@WebFilter(filterName = "xsltFilter", urlPatterns = "{/xml/*"})
public class XSLTFilter implements Filter{
    ... 필터 구현
}

 

· 위와 같은 경우 /xml/*에 일치하는 URL에 필터가 적용됨

· 두 개 이상의 URL 패턴을 지정하고 싶다면, 콤마로 구분해서 여러 URL 패턴을 지정

 

@WebFilter(filterNamer = "xsltFiler", urlPatterns = {"/xml/*", "/xsl/*"})
public class XSLTFilter implements Filter{

 

· @WebFilter 애노테이션의 주요 속성

   - urlPatterns: 필터를 적용할 URL 패턴 목록을 지정

   - servletName: 필터를 적용할 서블릿 이름 목록 지정

   - filterName: 필터의 이름을 지정

   - initParams: 초기화 파라미터 목록을 지정

   - dispatcherTypes: 필터를 적용할 범위를 지정, 열거 타입 DispatcherType에 정의된 값을 사용,

     기본값은 DispatchType.REQUEST 

 

요청 및 응답 래퍼 클래스

· 클라이언트의 요청과 응답을 변경할 때 ServletRequestWrapper, ServletResponseWrapper를 사용함

   - 위의 래퍼 클래스들로 할 수 있는 일:

   1. 요청 정보를 변경하여 최종 자원인 서블릿/JSP/HTML/기타 자원에 전달

   2. 최종 자원으로부터 응답을 변경하여 샐운 응답 정보를 클라이언트에 보냄

 

· 서블릿의 요청 래퍼와 응답 래퍼를 만들려면 javax.servlet 패키지에 정의된

ServletRequestWrapperServletResponseWrapper 클래스를 상속 받아 구현함

· 대부분 필터는 HTTP 프로토콜에 대한 요청·응답을  필터링 하므로 위의 두 클래스를 상속받아 알맞게 구현한 HttpServletRequestWrapper, HttpServletResponseWrapper 클래스를 상속 받아 구현하는 것을 권장함

   - java.servlet.http 패키지에 정의

   - HttpServletRequest, HttpServletResponse 인터페이스에 정의된 모든 메서드가 구현되어있음

 

· 필터를 통해 변경하고 싶은 요청 정보가 있으면 ,

HttpServletRequestWrapper 클래스를 상속받은 클래스를 만들고,

그 정보를 추출하는 메서드를 알맞게 재정의해서 변경한 정보를 제공하도록 구현하고,

구현한 래퍼 클래스의 객체를 FilterChain의 doFilter() 메서드에 넘져준다.

 

필터의 응용

· 필터를 적용하는 대표적인 기능:

   1. 사용자 인증

   2. 캐싱 필터

   3. 자원 접근에 대한 로깅

   4. 응답 데이터 변환(HTML 변환, 응답 헤더 변환, 데이터 암호화 등)

   5. 공통 기능 실행

 

로그인 검사 필터

· 사용자 인증을 구현하기 위해 인증 여부를 검사하는 필터를 사용할 수 있음

   - 웹브라우저의 요청은 서블릿/JSP에 전달되기 전에 필터를 통과하므로,

      필터에서 조건에 따라 알맞게 흐름 제어를 할 수 있음

   - 즉, 필터에서 로그인 여부를 판단 -> 로그인하지 않았다면 로그인 폼을 보여주는 페이지로 흐름을 이동시킬 수 있음

   - JSP/서블릿 등의 코드에 사용자 로그인 판단 여부를 확인하는 코드를 넣으면,

      회원 인증 정책이 변경될 때마다 관련된 모든 코드를 변경해야하는 문제가 발생

 

▶ 예시 - session에 "MEMBER" 속성이 존재하면 로그인한 것으로 판단하는 필터인 LoginCheckFilter 클래스

public class LoginCheckFilter implements Filter{
    @Override
    public void init(FilterConfig config) throws ServletException{}
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException{
        HttpServletRequest httpRequeset = (HttpServletRequest)request;
        HttpSession session = httpRequeset.getSession(false);
        
        boolean login =false;
        if(session != null)
            if(session.getAttribute("MEMBER") != null)
                login = true;
            
        //로그인한 상태라면
        if(login){
            //필터 체인의 다른 필터로 이동하고
            chain.doFilter(request, response);
        } else {
            //로그인하지 않은 상태라면 로그인 페이지로 이동
            RequestDispatcher dispatcher = request.getRequestDispatcher("/loginForm.jsp");
                dispatcher.forward(request, response);
        }
    }
    
    @Override
    public void destroy(){}
}

· LoginCheckFilter를 테스트하기 위해 web.xml 파일에 다음과 같은 필터 설정을 추가

<filter>
    <filter-name>LoginCheck</filter-name>
    <filter-class>filter.LoginCheckFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>LoginCheck</filter-name>
    <url-pattern>/board/*</url-pattern>
</filter-mapping>

 

loginForm.jsp 

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>로그인</title>
</head>
<body>
<form action="<%=request.getContextPath()%>/login.jsp">
    아이디<input type="text" name="memberId">
    비밀번호<input type="password" name="password" %>
    <input type="submit" value="로그인">
</form>
</body>
</html>

 

login.jsp 

<%@ page contentType="text/html; charset=utf-8" %>
<%
    String memberId = request.getParameter("memberId");
    session.setAttribute("MEMBER", memberId);
%>
<html>
    <body>
        로그인 처리
    </body>
</html>

 

TODO: XSL/T 필터

TODO: 캐릭터 인코딩 필터

 

출처

최범균의 JSP 2.3 웹 프로그래밍: 기초부터 중급까지

반응형

댓글