[Spring] Filter & Interceptor

young-gue Park·2023년 10월 8일
0

Spring

목록 보기
4/14
post-thumbnail

⚡ Filter & Interceptor


📌 Listener

🔷 특정 이벤트가 발생하기를 기다리다가 실행되는 객체

💡 이벤트(event)
특정한 사건 발생(버튼 클릭, 키보드입력, 컨테이너 빌드 완료, HTTP 요청 수신 등), 이벤트 소스는 이벤트가 발생한 근원지(객체)를 뜻한다.

  • 리스너의 종류는 굉장히 다양하기에 굳이 외우기보다는 필요할 때마다 찾아 쓰자.
  • 리스너 사용 방법은 두 가지가 있다.

1. Annotation

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
 * Application Lifecycle Listener implementation class MyListener
 *
 */
@WebListener
public class MyListener implements ServletContextListener {

    /**
     * Default constructor. 
     */
    public MyListener() {
        // TODO Auto-generated constructor stub
    }

	/**
     * @see ServletContextListener#contextDestroyed(ServletContextEvent)
     */
    public void contextDestroyed(ServletContextEvent sce)  { 
    	System.out.println("웹어플리케이션이 종료가 될때 호출 될 친구");
    }

	/**
     * @see ServletContextListener#contextInitialized(ServletContextEvent)
     */
    public void contextInitialized(ServletContextEvent sce)  { 
    	System.out.println("웹어플리케이션이 시작될때 호출 될 친구");
    }
	
}

2. Web.xml에 추가하기

  <listener>
  	<listener-class>com.ssafy.mvc.MyListener2</listener-class>
  </listener>
  <context-param>
  	<param-name>welcome</param-name>
  	<param-value>Bzeromo</param-value>
  </context-param>
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

public class MyListener2 implements ServletContextListener {

    public void contextDestroyed(ServletContextEvent sce)  { 
    	System.out.println("웹어플리케이션이 종료가 될때 호출 될 친구2");
    }
    
    
    public void contextInitialized(ServletContextEvent sce)  { 
    	System.out.println("웹어플리케이션이 시작될때 호출 될 친구2");
    	
    	ServletContext context = sce.getServletContext();
    	System.out.println(context.getInitParameter("welcome"));
    }
	
}

🖨 welcome이라는 이름을 가진 파라미터의 값(Bzeromo)을 가져와 콘솔창에 출력한다.


📌 Filter

🔷 요청과 응답 데이터를 필터링하여 제어, 변경하는 역할

  • 사용자의 요청이 Servlet에 전달되어지기 전에 Filter를 거침
  • Servlet으로부터 응답이 사용자에게 전달되어지기 전에 Filter를 거침
  • FilterChain을 통해 연쇄적으로 동작 가능

💡 FilterChain은 chain.doFilter(request, response)의 호출을 통해 다음 필터로 계속해서 이어질 수 있으며 필터체인이 끝날 때 서블릿을 호출할 수 있게끔 한다.

  • 리스너와 마찬가지로 애너테이션 혹은 Web.xml을 통해 만들 수 있다. 실습은 Web.xml을 통해 구현한 필터만을 다룬다.

💡 실습 코드에서는 간단하게 인코딩만 다루겠지만 필터 체인으로 다양한 필터를 다룰 수 있다.

  <filter>
  	<filter-name>MyFilter</filter-name>
  	<filter-class>com.bzeromo.mvc.MyFilter</filter-class>
  	<init-param>
  		<param-name>encoding</param-name>
  		<param-value>UTF-8</param-value>
  	</init-param>
  	
  </filter>
 
 <filter-mapping>
 	<filter-name>MyFilter</filter-name>
 	<url-pattern>/*</url-pattern>
 </filter-mapping> 

   <filter>
  	<filter-name>MyFilter2</filter-name>
  	<filter-class>com.bzeromo.mvc.MyFilter2</filter-class>
  	
  </filter>
 
 <filter-mapping>
 	<filter-name>MyFilter2</filter-name>
 	<url-pattern>/*</url-pattern>
 </filter-mapping> 

💡 url-pattern에서 /* 은 모든 경로의 요청이 들어올 때 필터로 이동할 것을 명한다.

🖥 MyFilter

import java.io.IOException;
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.annotation.WebFilter;

public class MyFilter implements Filter {

	public FilterConfig filterConfig;
	
	
	
    public MyFilter() {
    }

    //필터 초기화
    public void init(FilterConfig fConfig) throws ServletException {
    	filterConfig = fConfig;
    }
    //필터 종료
	public void destroy() {
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		
		//코드를 작성하면 
		System.out.println("서블릿 동작 이전에 할것");
		String encoding = this.filterConfig.getInitParameter("encoding");
		request.setCharacterEncoding(encoding); //encoding 부분에 직접 "UTF-8"을 넣어도 된다. 다만 유연성이 떨어진다.
		chain.doFilter(request, response); //다음 필터로 전달 , 서블릿 호출
		System.out.println("서블릿 동작 이후에 할것");
		
	}

}

🖥 MyFilter2

import java.io.IOException;
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.annotation.WebFilter;

public class MyFilter2 implements Filter {

	
	
	
    public MyFilter2() {
    }

    public void init(FilterConfig fConfig) throws ServletException {
    }
    //필터 종료
	public void destroy() {
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		
		//코드를 작성하면 
		System.out.println("서블릿 동작 이전에 할것2");
		chain.doFilter(request, response); //다음 필터로 전달 , 서블릿 호출
		//코드를 작성하면
		System.out.println("서블릿 동작 이후에 할것2");
		
	}


}

🖥 MyServlet

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class MyServlet
 */
@WebServlet("/MyServlet")
public class MyServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		
		System.out.println(request.getCharacterEncoding());
		
		
	
		response.getWriter().append("Served at: ").append(request.getContextPath());
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

🖨 요청이 들어오면 필터 1 -> 필터 2로 넘어가며 서블릿 동작 이전에 할 것 1, 2가 이루어진다. 모든 필터를 거치면 서블릿으로 넘어가 현재 인코딩 방식을 출력하고 서블릿 동작 이후에 할 것 1, 2가 이루어지며 종료된다.

💡 필터의 순서는 Web.xml에 작성한 순서대로 돌아간다. 애너테이션으로는 순서 지정이 굉장히 힘들어지기 때문에 필터는 보통 Web.xml을 활용한다.


📌 Interceptor

🔷 HandlerInterceptor를 구현한 것(또는 HandlerInterceptorAdapter를 상속한 것)

  • 요청을 처리하는 과정에서 요청을 가로채서 처리

  • 접근 제어(Auth), 로그(Log) 등 비즈니스 로직과 구분되는 반복적이고 부수적인 로직 처리

  • 주요 메서드

    1) preHandle(): boolean 타입, Controller 실행 이전에 호출되며 false를 반환하면 요청을 종료한다.

    2) postHandle(): Controller 실행 후 호출, 정상 실행 후 추가 기능 구현 시 사용, Controller에서 예외 발생 시 해당 메서드는 실행되지 않는다.

    3) afterCompletion(): 뷰가 클라이언트에게 응답을 전송한 뒤 실행, Controller에서 예외 발생 시, 네번째 파라미터로 전달이 되며 디폴트는 null이다. Controller에서 발생한 예외 혹은 실행 시간 같은 것들을 기록하는 등 후처리 시 주로 사용.

이번 실습부터는 bean과 필터 등이 등록되어 있는 web.xml이나 servlet-context.xml 등은 굳이 이곳에 올리지 않는다. 새 프로젝트를 만들더라도 계속해서 사용하던 것을 import해서 제작하기 때문에 그 부분은 변함이 없다. (물론 새로운 bean이나 Interceptor 등록은 이곳에 명시하지 않더라도 이루어져 있어야 한다.)

🖥 AInterceptor

package com.bzeromo.mvc.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

//인터셉터로 만들기 위해서
//1. 구현 : implements HandlerInterceptor (추천)
//2. 상속 : extends HandlerInterceptorAdapter (deprecated) 

public class AInterceptor implements HandlerInterceptor{
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		System.out.println("A : preHandle");
		return true;
	}
	
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		System.out.println("A : postHandle");
	}
	
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		System.out.println("A : afterCompletion");
	}

}

🖨 pre -> post -> after 순으로 콘솔에 출력된다.

💡 BInterceptorCInterceptor를 xml에 순서대로 등록하여 실행시킨다면 순서는 다음과 같다.
Apre -> Bpre -> Cpre -> Cpost -> Bpost -> Apost -> Cafter -> Bafter -> Aafter


인터셉터를 이용해 게시글 등록 시 로그인 확인

🖥 HomeController

package com.bzeromo.mvc.controller;

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.http.HttpRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Handles requests for the application home page.
 */
@Controller
public class HomeController {
	
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Locale locale, Model model) {
		Date date = new Date();
		DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
		
		String formattedDate = dateFormat.format(date);
		
		model.addAttribute("serverTime", formattedDate );
		
		return "home";
	}
	
	//regist 라고 하는 Get요청이 들어왔을 때
	@GetMapping("regist")
	public String registForm() {
		//로그인 유무를 파악을 하고 했으면 진행 안됐으면 돌아간다
		return "regist";
	}
	
	@PostMapping("regist")
	public String regist() {
		//예시
		//서비스를 호출해서 등록을 한다
		return "index";
	}
	
	
	@GetMapping("login")
	public String loginForm() {
		return "login";
	}
	
	
	@PostMapping("login")
	public String login(HttpSession session, String id,  String pw) {
		//user 관련 service를 호출해서 직접 내 사용자가 맞는지 check
		
		
		if(id.equals("bzeromo") && pw.equals("1234")) {
			session.setAttribute("id", id);
			return "redirect:/";
		}
		
		//아니면 로그인페이지로 돌려보내기
		return "redirect:/login";
	}
	
	
	@GetMapping("logout")
	public String logout(HttpSession session) {
		session.removeAttribute("id");
//		session.invalidate(); 
		return "redirect:/";
	}
	
		
}

🖥 home.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>Home</title>
</head>
<body>
	<c:choose>
		<c:when test="${empty id}">
			<a href="login">로그인 페이지</a>
		</c:when>
		<c:otherwise>
			${id} 님 반갑습니다. <a href="logout">로그아웃</a>
		</c:otherwise>
	</c:choose>



	<h1>Hello world!</h1>
	<P>The time on the server is ${serverTime}.</P>
	
	<!-- regist.jsp (게시글 등록) 로 보내는 링크 -->
	<a href="regist">게시글 등록</a>
	
</body>
</html>

🖥 login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 페이지</title>
</head>
<body>
	<h2>로그인 페이지입니다.</h2>
	
	<form action="login" method="POST">
		<input type="text" name="id"><br>
		<input type="password" name="pw"><br>
		<input type="submit"><br>
	
	</form>
</body>
</html>

🖥 regist.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시글 등록</title>
</head>
<body>
	<h2>게시글 등록 화면</h2>
	<form>
	
	
	</form>
</body>
</html>

🖥 LoginInterceptor

package com.ssafy.mvc.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.web.servlet.HandlerInterceptor;


public class LoginInterceptor implements HandlerInterceptor{
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		
		HttpSession session = request.getSession();
		if(session.getAttribute("id") == null) {
			response.sendRedirect("login");
			return false; //멈추고 돌려보낸다
		}
		return true; //컨트롤러로 요청을 보낸다
	}
	
}

🖨 인터셉터가 작동하면 로그인이 되어있지 않을 때 어떤 페이지로 이동하려 하든 로그인 페이지로 튕긴다.


추석 이후 예비군까지
개발 감각 찾기가 쉽지가 않다!

profile
Hodie mihi, Cras tibi

0개의 댓글