본문 바로가기

학원/복기

[Spring] 인터셉터(Interceptor)

인터셉터(Interceptor)

 

인터셉터는 요청 처리 메소드가 호출되기 전 또는 후에 삽입되어 실행될 명령을 작성하여 제공하는 기능이다.

  • 인터셉터를 사용하여 권한 관련 기능을 구현할 수 있다. - 요청 처리 메소드에서 권한 관련 명령..
  • 인터셉터 관련 클래스를 작성하여 Srping Bean Configuration Fiel(servlet-context.xml)에 인터셉터가 동작될 수 있도록 설정해주면 된다. 

 

인터셉터 vs 필터 

 

필터는 WAS의 제어를 받으며 FrontController 전에 실행되는 반면, 인터셉터는 FrontController 다음에 실행되며  FrontController 의 영향을 받는다.

 

인터셉터는 FrontController(스프링 컨테이너)에 의해 관리 될 수 있도록 만들어진다.

 

Filter는 web.xml에 필터를 등록해주고 url 주소를 매핑해주었지만,

인터셉터는  FrontController가 가져다 쓰는 스프링 컨테이너에 인터셉터를 등록해 원하는 페이지를 요청할 때 인터셉터가 동잘될 수 있도록 만들어 주면 된다. 

 

스프링 컨테이너는 servlet-context.xml에 요청 처리 메소드가 호출되기 전, 후에 인터셉터가 실행될 수 있도록 설정해주면 된다. 

 

인터셉터는 일종의 ADP 기술을 이용하는 것이라고 볼 수 있으며, 일반적으로 권한처리 할 때 많이 사용된다.

 

 

 


예제)

 

패키지 생성

 

 

Intercepter 클래스

  • Intercepter 클래스는 요청 처리 메소드가 호출되기 전 또는 후에 삽입되어 실행될 기능을 제공하는 클래스이다. 
  • Intercepter 클래스는 반드시 HandlerInterceptor 인터페이스를 상속받아 작성해주면 된다. 이 때, 필요한 메소드만 오버라이드 선언 해주면 된다.
    • -> HandlerInterceptor 인터페이스 내에 선언된 메소드들은 추상 메소드가 아니라 default 메소드이기 때문에 필요한 것만 오버라이드 할 수 있다. 
  • Srping Bean Configuration Fiel(servlet-context.xml)에 Srping Bean으로 등록하고 요청 처리 메소드 호출 전 또는 후에 실행되도록 설정해주어야 한다. 

 

 

관리자 관련 권한 처리를 위한 인터셉터 클래스 작성

 

요청 처리 메소드가 호출되기 전에 비로그인 사용자이거나 관리자가 아닌 사용자가 페이지를 요청한 경우 인위적으로 예외를 발생시켜 보자.  - 에러 페이지로 응답 처리할 것임 

 

preHandle 메소드

  • 요청 처리 메소드가 호출되기 전에 실행될 명령을 작성하기 위한 메소드이다.
  • false를 반환하면 요청 처리 메소드를 호출하지 않는다는 뜻이고 true를 반환하면 요청 처리 메소드를 호출한다는 뜻이다. (기본값은 true)

 

AdminAuthInterceptor

public class AdminAuthInterceptor implements HandlerInterceptor {
	//요청 처리 메소드가 호출되기 전에 실행될 명령을 작성하기 위한 메소드
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		HttpSession session=request.getSession();
		
		Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");
		
		//비로그인 사용자이거나 관리자가 아닌 사용자인 경우
		if(loginUserinfo == null || loginUserinfo.getStatus() != 9) {
			//1)
			//response.sendError(HttpServletResponse.SC_FORBIDDEN);//403 에러코드 전달
			//return false;//요청 처리 메소드 미호출
			
			//2)
			//request.getRequestDispatcher("usrinfo/user_error.jsp").forward(request, response);
			//return false;//요청 처리 메소드 미호출
			
			//3) ExceptionHandler 사용
			throw new BadRequestException("비정상적인 요청입니다.");
		}
		return true;//요청 처리 메소드 호출
	}
}

 

 

postHandle 메소드

  • 요청 처리 메소드가 호출되어 반환된 뷰 이름으로 ViewResolver가 뷰(View)를 생성하기 전에 실행될 명령을 작성하기 위한 메소드이다.
  • ModelAndView 객체를 제공받아 VeiwName 또는 Model 객체의 속성값을 저장(변경)하기 위해 사용한다. 

 

//요청 처리 메소드가 호출되어 뷰(View)가 생성되기 전에 실행될 명령을 작성하기 위한 메소드
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
		ModelAndView modelAndView) throws Exception {
	// TODO Auto-generated method stub
	HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

 

afterCompletion 메소드

  • 요청 처리 메소드가 호출되어 반환된 뷰이름으로 ViewResolver가 뷰(View)를 생성한 후에 실행될 명령을 작성하는 메소드이다. 
  • 뷰(View)를 변경하기 위해 사용한다.
//요청 처리 메소드가 호출되어 뷰가 생성된 후에 실행될 명령을 작성하는 메소드
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
		throws Exception {
	// TODO Auto-generated method stub
	HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}

 


 

로그인 사용자 관련 권한 처리를 위한 인터셉터 클래스 작성

 

요청 처리 메소드가 호출되기 전에 비로그인 사용자가 페이지를 요청한 경우 인위적으로 예외를 발생시킬 것이다. - 에러 페이지로 응답 처리할 것임 

public class LoginAuthInterceptor implements HandlerInterceptor {
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		HttpSession session=request.getSession();
		
		Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");
		
		//비로그인 사용자인 경우
		if(loginUserinfo == null || loginUserinfo.getStatus() != 9) {
			throw new BadRequestException("비정상적인 요청입니다.");
		}
		return true;//요청 처리 메소드 호출
	}
}

 

 

** 실제 프로젝트에서는 리다이렉트 이동을 시키되 쿼리 스트링, 세션 등을 이용해 return url을 사용해보기..(로그인 페이지로 이동 등)

 

 


 

UserinfoController에서 기존 메소드를 수정해주자

//회원정보를 입력받기 위한 뷰이름을 반환하는 요청 처리 메소드
// => 비로그인 사용자 또는 관리자가 아닌 사용자가 페이지를 요청할 경우 인위적 예외 발생

인터셉터 사용 전 
@RequestMapping(value = "/write", method = RequestMethod.GET)
public String write(HttpSession session) throws BadRequestException {
	Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");
	if(loginUserinfo == null || loginUserinfo.getStatus() != 9) {
		//throw new Exception("비정상적인 요청입니다.");
		throw new BadRequestException("비정상적인 요청입니다.");
	}
	return "userinfo/user_write";
}

인터셉터 사용 후 
@RequestMapping(value = "/write", method = RequestMethod.GET)
public String write(HttpSession session) throws BadRequestException {
	return "userinfo/user_write";
}

 

//USEINFO 테이블에 저장된 모든 회원정보를 검색하여 속성값으로 저장하여 회원목록을 출력하는
//뷰이름을 반환하는 요청 처리 메소드
// => 비로그인 사용자가 페이지를 요청할 경우 인위적 예외 발생
	
인터셉터 사용 전 

@RequestMapping("/list")
public String list(Model model, HttpSession session) throws BadRequestException {
	Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");
	if(loginUserinfo == null) {
		throw new BadRequestException("비정상적인 요청입니다.");
	}
		
	model.addAttribute("userinfoList", userinfoService.getUserinfoList());
	return "userinfo/user_list";
}

인터셉터 사용 후 

=> 인터셉터를 사용하여 권한 관련 처리 기능 구현 - 요청 처리 메소드에서 권한 관련 명령 미작성
@RequestMapping("/list")
public String list(Model model, HttpSession session) throws BadRequestException {
	model.addAttribute("userinfoList", userinfoService.getUserinfoList());
	return "userinfo/user_list";
}

 

//아이디를 전달받아 USEINFO 테이블에 저장된 회원정보를 검색하여 속성값으로 저장하여 
//회원정보를 출력하는 뷰이름을 반환하는 요청 처리 메소드
// => 비로그인 사용자가 페이지를 요청할 경우 인위적 예외 발생

인터셉터 사용 전 

@RequestMapping("/view")
public String view(@RequestParam String userid, Model model, HttpSession session) throws BadRequestException, UserinfoNotFoundException {
	Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");
	if(loginUserinfo == null) {
		throw new BadRequestException("비정상적인 요청입니다.");
	}
		
	model.addAttribute("userinfo", userinfoService.getUserinfo(userid));
	return "userinfo/user_view";
}

인터셉터 사용 후

@RequestMapping("/view")
public String view(@RequestParam String userid, Model model) throws UserinfoNotFoundException {
	model.addAttribute("userinfo", userinfoService.getUserinfo(userid));
	return "userinfo/user_view";
}

 

 

//아이디를 전달받아 USEINFO 테이블에 저장된 회원정보를 검색하여 속성값으로 저장하여 
//회원정보를 변경하는 뷰이름을 반환하는 요청 처리 메소드
// => 비로그인 사용자 또는 관리자가 아닌 사용자가 페이지를 요청할 경우 인위적 예외 발생

인터셉터 사용 전

@RequestMapping(value = "/modify", method = RequestMethod.GET)
public String modify(@RequestParam String userid, Model model, HttpSession session) throws BadRequestException, UserinfoNotFoundException {
	Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");
	if(loginUserinfo == null || loginUserinfo.getStatus() != 9) {
		throw new BadRequestException("비정상적인 요청입니다.");
	}
		
	model.addAttribute("userinfo", userinfoService.getUserinfo(userid));
	return "userinfo/user_modify";
}

인터셉터 사용 후

@RequestMapping(value = "/modify", method = RequestMethod.GET)
public String modify(@RequestParam String userid, Model model) throws UserinfoNotFoundException {
	model.addAttribute("userinfo", userinfoService.getUserinfo(userid));
	return "userinfo/user_modify";
}

 

//아이디를 전달받아 USEINFO 테이블에 저장된 회원정보를 삭제하고 회원목록 출력페이지를
//요청할 수 있는 URL 주소를 클라이언트에게 전달하여 응답 처리하는 요청 처리 메소드
// => 비로그인 사용자 또는 관리자가 아닌 사용자가 페이지를 요청할 경우 인위적 예외 발생

인터셉터 사용 전

@RequestMapping("/remove")
public String remove(@RequestParam String userid, HttpSession session) throws BadRequestException, UserinfoNotFoundException {
	Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");
	if(loginUserinfo == null || loginUserinfo.getStatus() != 9) {
		throw new BadRequestException("비정상적인 요청입니다.");
	}
		
	userinfoService.removeUserinfo(userid);
		
	if(loginUserinfo.getUserid().equals(userid)) {
		return "redirect:/userinfo/logout";
	}
		
	return "redirect:/userinfo/list";
}

인터셉터 사용 후

@RequestMapping("/remove")
public String remove(@RequestParam String userid, HttpSession session) throws UserinfoNotFoundException {
	Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");
	userinfoService.removeUserinfo(userid);
		
	if(loginUserinfo.getUserid().equals(userid)) {
		return "redirect:/userinfo/logout";
	}
		
	return "redirect:/userinfo/list";
}

 

 

중복된 코드를 없앨 수 있다는 장점이 있다.


 

작성한 두개의 인터셉터 클래스가 동작할 수 있도록 만들어 보자

 

 

sevlet-context.xml

 

Intercepteor 클래스를 스프링 빈으로 등록

<beans:bean class="xyz.itwill10.util.AdminAuthInterceptor" id="adminAuthInterceptor"></beans:bean>
<beans:bean class="xyz.itwill10.util.LoginAuthInterceptor" id="loginAuthInterceptor"></beans:bean>

 

 

interceptors : interceptor 엘리먼트로 등록하기 위한 엘리먼트

  • interceptor : 인터셉터 기능을 제공받기 위한 규칙을 설정하는 엘리먼트
  • mapping : 인터셉터가 실행될 요청 처리 메소드의 페이지 요청 URL 주소를 설정하는 엘리먼트
    • path 속성에 요청 처리 메소드가 호출될 요청 URL 주소를 속성값으로 설정한다.
  • ref : 인터셉터 기능을 제공할 객체(Spring Bean)을 설정하기 위한 엘리먼트
    • bean 속성에 Interceptor 클래스의 Spring Bean 식별자(beanName)를 속성값으로 설정한다.
	<!-- interceptors : interceptor 엘리먼트로 등록하기 위한 엘리먼트 -->
	<interceptors>
		<interceptor>
			<!-- 인터셉터가 실행될 요청 처리 메소드의 페이지 요청 URL 주소를 설정 -->
			<mapping path="/userinfo/write"/>
			<mapping path="/userinfo/modify"/>
			<mapping path="/userinfo/remove"/>
			<!-- Interceptor 클래스의 Spring Bean 식별자(beanName)를 속성값으로 설정 -->
			<beans:ref bean="adminAuthInterceptor"/>
		</interceptor>
		
		<interceptor>
			<mapping path="/userinfo/list"/>
			<mapping path="/userinfo/view"/>
			<beans:ref bean="loginAuthInterceptor"/>
		</interceptor>
	</interceptors>

 

 

mapping 엘리먼트의 path 속성값으로 [*] 패턴문자를 사용하여 요청 URL 주소를 설정하는 것도 가능하다.

  • [/*] : 폴더의 모든 페이지를 요청한 경우
  • [/**] : 최상위 폴더 및 하위 폴더의 모든 페이지를 요청한 경우
<!-- 폴더의 모든 페이지를 요청한 경우-->
<mapping path="/*"/> 
<!-- 최상위 폴더 및 하위 폴더의 모든 페이지를 요청한 경우 -->
 <mapping path="/**"/>

 

 

만약 권한이 없는 사용자가 list를 요청했을 경우 user_error.jsp 로 이동한다

 

 

 

페이지가 많을 경우 [*]을 사용하는 것을 권장한다.

 

exclude-mapping : 인터셉터가 실행되지 않는 페이지의 요청 URL 주소를 설정하는 엘리먼트

<interceptor>
	<mapping path="/userinfo/*"/>
	<!-- 인터셉터가 실행되지 않는 페이지의 요청 URL 주소를 설정 -->
	<exclude-mapping path="/userinfo/login"/>
	<beans:ref bean="loginAuthInterceptor"/>
</interceptor>