[Java] 54. Spring framework에서 Web filter를 사용하는 방법


Study/Java  2021. 6. 29. 13:47

안녕하세요. 명월입니다.


이 글은 Spring framework에서 Web filter를 사용하는 방법에 대한 글입니다.


우리가 웹 서비스를 작성하다 보면 세션을 관리던가 메모리 관리등의 브라우저에서의 요청에 대한 전체적인 관리 및 설정이 필요한 경우가 있습니다.

예를 들어, 로그인을 한 후에 그 이후에 서버 측에서는 유저의 세션이 유지가 되어 있는지 확인을 해야합니다. 유저의 세션이 유지되어 있으면 로그인을 한 상태가 되는 것입니다.

당연하지만 세션이 유지되어 있지 않으면 해당 페이지에서 빠져나와 로그인을 요구해야 합니다.

※ 참고로 세션 값이란 서버 측에서 관리하는 데이터인데, 그 여부는 브라우저의 쿠키 값에 세션 키를 넣어 브라우저의 쿠키 값이 초기화 또는 만료가 되지 않는 이상 서버에서는 그 키로 서버측에 저장되어 있는 값을 가져오는 값을 말합니다.


방법으로는 Controller에서 함수를 호출하는 상단 부분에 세션 체크하는 부분을 넣어도 해결은 됩니다.

그러나 좀 더 간단한 방법으로는 브라우저에서 호출이 있을 때, 무조건 호출하는 함수 Filter를 이용해서 세션 여부를 확인하는 방법도 있습니다.


필터는 먼저 web.xml에서 어떤 url 패턴으로 어떤 클래스를 호출할 지 설정합니다.

<!-- *.html 확장자로 오는 요청은 WebFilter의 필터명을 호출한다. -->
<filter-mapping>
  <filter-name>WebFilter</filter-name>
  <url-pattern>*.html</url-pattern>
</filter-mapping>
<!-- 필터 -->
<filter>
  <!-- 필터명을 설정 - WebFilter -->
  <filter-name>WebFilter</filter-name>
  <!-- 호출할 클래스 이름을 설정 (package 포함) -->
  <filter-class>controller.filter.WebFilter</filter-class>
  <!-- 클래스에서 사용할 파라미터 데이터 -->
  <init-param>
    <param-name>passPage</param-name>
    <param-value>/,/index.html,/error.html</param-value>
  </init-param>
</filter>

필터 매핑(filter-mapping) 태그에서는 브라우저에서 요청하는 매핑 패턴을 설정합니다. 저의 경우는 Spring framework에서 기본적으로 *.html로 오는 패턴을 filter를 거치게 만들었습니다.

만약 모든 페이지를 설정하려고 하면 별 표시(*)로 설정하면 됩니다. 그런데 웹 서비스에서 요청하는 리소스가 웹 페이지만 있는 것이 아니고 image나 css 파일(스타일 시트 파일), js 파일(자바스크립트 파일) 등도 있기 때문에 정확하게 설정하는 편이 좋습니다.

필터 태그에서는 필터 매핑(filter-mapping)에서 설정한 페이지가 Controller의 호출 함수 매핑 전에 호출될 클래스를 설정하는 것입니다.

저는 controller.filter의 패키지의 WebFilter 클래스를 호출합니다.

init-param은 filter 클래스에서 사용할 변수 값으로 저는 filter 채크를 피하기 위한 페이지, User 로그인 세션이 필요없는 페이지를 설정했습니다.


이제 WebFilter 클래스를 만들겠습니다.

package controller.filter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class WebFilter implements Filter {

  private List<String> passUrl = null;
  private String contextPath = null;

  // 초기화 함수
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    // 웹 페이지의 root페이지의 url를 취득한다.
    contextPath = filterConfig.getServletContext().getContextPath();
    passUrl = new ArrayList<String>();
    // web.xml에서 설정했던 init-param를 가져와서 설정한다.
    String initparam = filterConfig.getInitParameter("passPage");
    // 콤마(,)의 기준으로 String을 분할하고 passUrl 맴버 변수에 값을 넣습니다.
    for (String ignoredPath : initparam.split(",")) {
      passUrl.add(contextPath + ignoredPath);
    }
  }

  // 필터 함수, 브라우저에서 요청이 오면 필터 매핑에 의해 호출된다.
  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    // 파라미터 값을 캐스팅한다.
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;
    HttpSession session = req.getSession();
    // 요청 URL 취득
    String url = req.getRequestURI();
    // passUrl 맴버 변수에 해당 페이지 요청이 있는 지 확인
    for (String buf : passUrl) {
      // 있으면
      if (buf.equals(url)) {
        // 그대로 Controller 요청으로 넘어간다.
        chain.doFilter(req, res);
        return;
      }
    }
    // User 세션에 값이 있으면
    if (session.getAttribute("USER") != null) {
      // 그대로 Controller 요청으로 넘어간다.
      chain.doFilter(req, res);
      return;
    }
    // User 세션도 없고 passUrl에 포함되어 있지 않으면 에러를 발생한다.
    throw new RuntimeException();
  }
}

Filter를 설정했습니다. init 함수에서는 프로젝트가 시작될 때 호출되는 함수로 기본적으로 세션 체크를 하지 않을 데이터를 설정합니다.

doFilter 함수는 브라우저에서 요청이 있을 때마다 호출되는 함수입니다. 요청 url를 분석해서 패스할 페이지인지 아닌지 선별하고 패스할 페이지가 아니면 세션 체크를 하고 세션이 없다면 에러를 발생합니다.

package controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class Home {
  // 요청 url 패턴 (index.html)
  @RequestMapping(value = "/index.html")
  public String index(ModelMap modelmap, HttpSession session, HttpServletRequest req, HttpServletResponse res) {
    // modelmap에 데이터 설정
    modelmap.addAttribute("Data", "index");
    // 세션 설정
    session.setAttribute("USER", "LOGIN");
    return "index";
  }

  // 요청 url 패턴 (test.html)
  @RequestMapping(value = "/test.html")
  public String test(ModelMap modelmap, HttpSession session, HttpServletRequest req, HttpServletResponse res) {
    // modelmap에 데이터 설정
    modelmap.addAttribute("Data", "test");
    // 세션 삭제
    session.removeAttribute("USER");
    return "index";
  }

  // 요청 url 패턴 (error.html)
  @RequestMapping(value = "/error.html")
  public String error(ModelMap modelmap, HttpSession session, HttpServletRequest req, HttpServletResponse res) {
    // modelmap에 error code 설정
    modelmap.addAttribute("Data", "error code :  " + res.getStatus());
    // view의 파일명
    return "error";
  }
}

위 Controller은 세가지 요청에 대응합니다.

index.html은 web.xml에서 설정된 패스 페이지로 세션이 없어도 페이지가 표시됩니다. 그리고 index 함수를 보면 세션을 설정합니다.

test.html은 web.xml에서 설정이 되어 있지 않기 때문에 doFilter에서 세션 체크를 합니다. test 함수에는 세션을 삭제하는 로직이 포함되어 있습니다.

error는 이전 글에서 설명한 error 페이지 입니다.

링크 - [Java] 53. 웹 서비스(Web service)에서 에러페이지 처리하는 방법


여기서 웹 서비스를 기동하고 표시되는 페이지는 index.html입니다. 그러나 index 함수에서 세션을 설정하니 그런다음 test.html를 요청해도 filter에서 세션 인증 확인이 되기 때문에 에러 없이 표시됩니다.

그런데 여기서 test.html에 매핑되어 있는 test 함수에서는 세션을 삭제하는 로직이 있습니다.

즉, test.html를 재갱신(F5)를 하게 되면 에러 페이지가 발생합니다.

실무에서는 로그인 페이지에서 User의 정보를 세션에 넣고 다른 페이지에서는 Filter로 페이지 세션 확인으로 페이지 로그인 여부를 확인할 수 있습니다.


filter 기능의 주요 목적은 세션 등을 통해서 로그인 확인을 하여 서버 응답을 제어할 수 있습니다.


그러나 여기에 추가적으로 브라우저의 모든 요청은 filter를 거친다라는 부분을 이용해서 페이지 요청 전의 공통 환경 설정등을 할 수 있습니다.

예를 들면, 최근에 사용된 접속 브라우저의 로그 설정과 쿠키 정보 수집, 유저의 행동 추적등으로 사용할 수 있습니다.

그러나 filter에 너무 많은 처리와 로직이 포함되면 시스템이 느려지는 부작용도 있으니 사양에 맞게 작성되면 좋을 듯 싶습니다.


여기까지 Spring framework에서 Web filter를 사용하는 방법에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.