개발자/Spring

[SPRING] Interceptor의 의미와 구현 및 실습

푸루닉 2023. 1. 10. 15:19

1. 인터셉터

컨트롤러의 '핸들러'를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 일종의 필터를 뜻한다.

interceptor란 단어는 '낚아채다'라는 의미이다. 해당 단어의 의미와 같이 사용자 요청에 의해 서버에 들어온 

Request 객체를 컨트롤러의 핸들러(사용자가 요청한 url에 따라 실행되어야 할 메서드, 이하 핸들러)로 

도달하기전에 낚아채서 개발자가 원하는 추가적인 작업을 한 후 핸들러로 보낼 수 있도록 해주는 것이 인터셉터이다.

 

2. 사용하는 이유

개발자는 특정 Controller의 핸들러가 실행되기 전이나 후에 추가적인 작업을 원할때 Interceptor를 사용한다.

(추가적인 작업에는 로그엔체크, 권한 체크 등이 있다.)

 

권한 체크 예를 통해서 개발자가 인터셉터의 어떠한 이점때문에 사용하기를 원하는지 살펴볼 것이다.

개발자가 관리자 계정만이 실행할 수 있는 Controller 핸들러를 작성한다고 가정한다.

개발자는 오직 관리자 계정만 실행할 수 있도록 하기 위해 핸들러에 접근하는 사용자가 관리자 인지 확인하는 세션 체크 코드를 각 핸들러에 작성해줘야한다.

작성해줘야 할 핸들러 수가 적다면 문제가 되지 않곘지만 적용해야할 핸들러가 수천개가 된다면??

 

크게 두 가지 문제가 생긴다.

1) 메모리낭비, 서버의 부하가 늘어난다.(코드 또한 길어진다.)

@GetMapping("/board")							// "/board" 로 접근할 때
	public ModelAndView board(HttpSession session) {// session을 참조하여
		ModelAndView mav = new ModelAndView();
		if(session.getAttribute("login") == null) {	// 로그인이 되지 않았다면
			mav.setViewName("redirect:/login");		// 로그인 페이지로 강제 이동시킨다.
		}else {
			mav.setViewName("board");
		}
		return mav;	// 이미 로그인이 되어 있으면 게시판으로 이동
	}

로그인 권한이 필요한 하나의 링크를 생성할 때마다 위의 코드를 적어줘야한다.

2) 코드의 누락 발생

많은 링크를 사용하다보면 사람의 실수로 권한부여시 누락이 생길 수도 있다. 코드의 누락으로 인해 로그인 하지 않아도 중요정보를 보거나, 관리자모드로 접근이 가능하다는 등의 큰 피해를 남길 수 있다.

3. 인터셉터의 동작위치

4. 구현 및 실습

servlet-context.xml 스프링 빈 추가

	<!-- interceptor를 활성화 하기 위한 스프링 빈 -->
	<mvc:interceptors>
		<mvc:interceptor>
<!-- 			<mvc:mapping path="/board/write"/> -->
<!-- 			<mvc:mapping path="/board/modify/**"/> -->
<!-- 			<mvc:mapping path="/board/delete/**"/> -->
			<!-- 인터셉터를 적용할 주소 -->
			<mvc:mapping path="/board/**"/>
			<!-- 인터셉터 대상에서 제외시킬 주소 -->
			<mvc:exclude-mapping path="/board"/>
			
			<beans:bean id="loginInterceptor" class="com.itbank.interceptor.LoginInterceptor"/>
		</mvc:interceptor>
	</mvc:interceptors>

로그인 없이 게시판은 접근할 수 있으나 글쓰기를 비롯한 crud는 사용할 수 없게 설정했다.(mapping으로

 

LoginInterceptor(beans에 적은 class에 맞게)

package com.itbank.interceptor;

import java.net.URLEncoder;

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

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.itbank.model.Member5DTO;


// 저장 - 우클릭- source - override/implements method

// 스프링 인터셉터 : 요청을 가로채서, 사전 코드를 실행할 수 있다. 여러 주소에 대해 일괄 적용 가능
// Handle : 컨트롤러의 메서드를 나타낸다
// 1) preHandle : 요청이 컨트롤러에 도달하기 전에 실행되는 함수 (DispathcerServlet -> controller)
// 2) postHandle : 컨트롤러의 메서드가 수행된 이후 실행되는 함수 (return mav 이후)
// 3) afterCompletion : 응답이 클라이언트 방향으로 출발한 이후 실행되는 함수 (forward/redirect 이후)

public class LoginInterceptor extends HandlerInterceptorAdapter {

	// preHandle은 반환형이 boolean 이다.
	// true를 반환하면, 예정되어 있던 컨트롤러의 메서드(handler)를 수행한다
	// false를 반환하면, 예정되어 있던 컨트롤러의 메서드(handler)를 수행하지 않는다.
	// 즉, preHandle 의 반환값은 예정대로 진행할 것인가? 에 대한 답이다.
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		System.out.println("preHandle");
		System.out.println("handler : " + handler);
		
//		response.getWriter().append("<h1>Hello, Interceptor<h1>");
		
		HttpSession session = request.getSession();
		Member5DTO member = (Member5DTO)session.getAttribute("member");
		
		String url = request.getRequestURL().toString();	// 요청받은 주소
		// url 값을 utf-8 형태로 넘기세요(특수기호를 주소창형식에 맞게 변환해서 보내주는 형식 URLEncoder)
		url = URLEncoder.encode(url, "utf-8");
		
		// url을 파라미터값으로 넘겨준다.
		if(member == null) {
			System.out.println("인터셉터에 의해 로그인 페이지로 이동합니다.");
			response.sendRedirect(request.getContextPath() + "/login?url=" + url);
			return false;
		}
		
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		System.out.println("postHandle");
		super.postHandle(request, response, handler, modelAndView);
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		System.out.println("afterCompletion");
		super.afterCompletion(request, response, handler, ex);
	}
	


}

request.getRequestURL()를 이용해서 URL을 받아온 후 toString을 통해 String url에 넣어준다.

넘겨줄때 utf-8처리를 하기 위해 URLEncoder 처리를 해준다.

 

Controller(login에서 처리)

	@PostMapping("/login")
	public ModelAndView login(Member5DTO dto, HttpSession session, String url) {
		ModelAndView mav = new ModelAndView();
		
		// prehandler로 먼저 실행된 결과의 url 파라미터값을 받아온다.
		System.out.println("로그인 이후 이동할 주소 : " + url);
		Member5DTO login = member5Service.login(dto);
		session.setAttribute("member", login);
		
		if(url == null) {
			session.setAttribute("member", login);
			mav.addObject("msg", login != null ? "로그인 성공" : "아이디와 비번을 확인해주세요");
			mav.addObject("url", login != null ? "/day09_spring_intercepter/" : "/day09_spring_intercepter/login");
			mav.setViewName("alert");
		}else {
			mav.addObject("msg", "로그인 성공");
			mav.addObject("url", url);
			mav.setViewName("alert");
		}
		return mav;
	}

로그인을 하지않으면 접근을 막아놓은경우 로그인 화면으로 가게 설정되어있는데 이 상황에서 로그인을 하게되면 인덱스 페이지로 가게 된다. 그래서 파라미터로 url값을 받아와서 로그인 이후 자연스럽게 해당 url로 넘어갈 수 있도록 해준다.

 

jsp(script용 페이지)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ include file="header.jsp" %>

<script>
    const msg = "<c:out value='${msg}'/>";
    const url = "<c:out value='${url}'/>";
    alert(msg);
    location.href = url;
</script>

</body>
</html>