[Spring] HandlerInterceptor와 이를 활용한 로그인 처리

HyeJin Jeon·2020년 5월 22일
5

Spring

목록 보기
2/2
post-custom-banner

1. HandlerInterceptor 란?

HandlerInterceptor는 특정한 URI 호출을 '가로채는' 역할을 합니다. 이를 이용하여 기존 컨트롤러의 로직을 수정하지 않고도, 사전이나 사후 제어가 가능합니다.

1.1 HandlerInterceptor 메소드

▶ preHandle(request, response, handler)

지정된 컨트롤러의 동작 이전에 수행할 동작 (사전 제어).

▶ postHandle(request, response, handler, modelAndView)

지정된 컨트롤러의 동작 이후에 처리할 동작 (사후 제어).
Spring MVC의 Dispatcher Servlet이 화면을 처리하기 전에 동작.

▶ afterCompletion(request, reponse, handler, exception)

Dispatcher Servlet의 화면 처리가 완료된 이후 처리할 동작.

1.2 HandlerInterceptor vs Filter

HandlerInterceptor vs Filter

두 기능 모두 특정 URI에 접근할 때 제어하는 용도로 사용됩니다. 두 기능의 가장 큰 차이는 Context(실행 영역)에 있습니다.

Filter는 웹 어플리케이션 내에서 동작하기 때문에 Spring Context에 접근하기 어렵습니다.

반면 Interceptor의 경우 Spring 영역 내에서 동작하기 때문에, Spring Context에 접근하기 용이합니다.

2. HandlerInterceptor 사용 설정

HandlerInterceptorAdaptor를 상속한 Interceptor 클래스를 작성합니다.

package org.sample.interceptor;

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

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

public class SampleInterceptor extends HandlerInterceptorAdaptor {
    
    @Override
    public boolean preHandler(
    	HttpServletRequest request, 
    	HttpServletResponse response,
        Object handler) throws Exception {
        
        System.out.println("pre handle........");
        
        return true;
    }

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

2.1 servlet-context.xml (SpringFramework)

servlet-context.xml

<beans:bean id="sampleInterceptor" 
            class="org.sample.interceptor.SampleInterceptor"/>

<interceptors>
  <interceptor>
    <mapping path="/doA"/>
    <mapping path="/doB"/>
    <beans:ref bean="sampleInterceptor"/>
  </interceptor>
</interceptors>

/doA URI 실행 결과

pre handle........
doA...............
post handle.......

2.1 Configuration Class (SpringBoot)

SampleWebMvcConfig.java

package org.sample;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.sample.interceptor.SampleInterceptor;

@Configuration
public class SampleWebMvcConfig extends WebMvcConfigurerAdapter {
	
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    	
        List<String> URL_PATTERNS = Arrays.asList("/doA", "doB");
        registry.addInterceptor(new SampleInterceptor())
        	.addPathPatterns(URL_PATTERNS)
            	.excludePathPatterns("/login")
            	.excludePathPatterns("/admin/**/");
    }
}

addInterceptors 메서드를 구현을 할 때 위와 같이 구현을 하며, 인터셉터 구현 시 메서드 체이닝을 되어있어 연속적으로 다른 추가 패턴도 등록할 수 있습니다.

addPathPatterns는 적용할 url 패턴을 설정합니다. *, **등을 사용한 URI Pattern String을 전송하거나, URI Pattern 배열을 전송할 수 있습니다.

excludePathPatterns의 경우 인터셉터를 제외할 url 패턴을 등록하는 메서드로써 해당 url로 접근 시에는 인터셉터를 적용하지 않게 됩니다.

3. 로그인 처리

3.1 LoginInterceptor

로그인한 사용자에 대해서 postHandle()을 통해 HttpSession에 보관하는 처리.

LoginInterceptor.java

public class LoginInterceptor extends HandlerInterceptorAdapter {

    private static final String LOGIN = "login";
    private static final Logger logger = LoggerFactory.getLogger(loginInterceptor.class);
    
    @Override
    public void postHadler (HttpServletRequest request, 
    	HttpServletResponse response,
        Object handler,
        ModelAndView modelAndView) throws Exception {
        
        HttpSession session = request.getSession();
        ModelMap modelMap = modelAndView.getModelMap();
        Object userVO = modelMap.get("userVO");
        
        if(userVO != null) {
        
            // 로그인 성공시 Session에 저장후, 초기 화면 이동
            logger.info("new login success");
            session.setAttribute(LOGIN, userVO);
            response.sendRedirect("/");
        }
    }
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
    	HttpServletResponse response,
        Object handler) throws Exception {
        
        HttpSession session = request.getSession();
        
        if(session.getAttribute(LOGIN) != null) {
        
            // 기존 HttpSession에 남아있는 정보가 있는 경우 이를 삭제
            logger.info("clear login data before");
            session.removeAttribute(LOGIN);
        }
        
        return true;
    }
}

servlet-context.xml

<beans:bean id="loginInterceptor" 
            class="org.sample.interceptor.LoginInterceptor"/>

<interceptors>
  <interceptor>
    <mapping path="/user/login"/>
    <beans:ref bean="loginInterceptor"/>
  </interceptor>
</interceptors>

3.2 AuthInterceptor

특정 경로에 사용자가 접근하는 경우 현재 사용자가 로그인한 상태인지를 체크하는 처리.

AuthInterceptor.java

public class AuthInterceptor extends HandlerInterceptorAdapter {

    private static final Logger logger = LoggerFactory.getLogger(AuthInterceptor.class);
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
    	HttpServletResponse response,
        Object handler) throws Exception {
        
        HttpSession session = request.getSession();
        
        if(session.getAttribute("login") != null) {
        
            logger.info("current user is not logined");
            
            // 로그인하지 않은 사용자일 경우 로그인 페이지로 이동
            response.sendRedirect("/user/login");
            return false;
        }
        
        // 로그인한 사용자일 경우 Controller 호출
        return true;
    }
}

servlet-context.xml

<beans:bean id="loginInterceptor" 
            class="org.sample.interceptor.LoginInterceptor"/>
<beans:bean id="authInterceptor" 
            class="org.sample.interceptor.AuthInterceptor"/>

<interceptors>
  <interceptor>
    <mapping path="/user/login"/>
    <beans:ref bean="loginInterceptor"/>
  </interceptor>
  <interceptor>
    <!-- login 인증이 필요한 URI 들 -->
    <mapping path="/sboard/register"/>
    <mapping path="/sboard/modify"/>
    <mapping path="/sboard/remove"/>
    <beans:ref bean="authInterceptor"/>
  </interceptor>
</interceptors>

3.3 자동 페이지 이동

로그인이 요구되어 로그인 페이지로 접근한 사용자가 로그인한 이후에 원래 경로로 이동시켜주는 동작.

AuthInterceptor.java

// 로그인 페이지 이동 전, 현재 페이지를 Session에 저장
private void saveDest(HttpServletRequest req) {

    String uri = req.getRequestURI();
    Stirng query = req.getQueryString();
    
    // 기존 URI에 parameter가 있을 경우, 이를 포함
    if(query == null || query.equals("null")) {
    	query = "";
    } else {
        query = "?" + query;
    }
    
    if(req.getMethod().equals("GET")) {
        logger.info("dest: " + (uri + query));
        req.getSession().setAttribute("dest", uri + query);
    }
}

@Override
public boolean preHandle(HttpServletRequest request, 
    HttpServletResponse response,
    Object handler) throws Exception {
        
    HttpSession session = request.getSession();
        
    if(session.getAttribute("login") == null) {
        
        logger.info("current user is not logined");
        
        saveDest(request);
        
        // 로그인하지 않은 사용자일 경우 로그인 페이지로 이동
        response.sendRedirect("/user/login");
        return false;
    }
        
    // 로그인한 사용자일 경우 Controller 호출
    return true;
}

LoginInterceptor.java

@Override
public void postHadler (HttpServletRequest request, 
    HttpServletResponse response,
    Object handler,
    ModelAndView modelAndView) throws Exception {
        
    HttpSession session = request.getSession();
    ModelMap modelMap = modelAndView.getModelMap();
    Object userVO = modelMap.get("userVO");
        
    if(userVO != null) {
        
        // 로그인 성공시 Session에 저장후, 초기 화면 이동
        logger.info("new login success");
        session.setAttribute(LOGIN, userVO);
        
        // 이전 destination 불러오기
        // response.sendRedirect("/");        
        Object dest = session.getAttribute("dest");
        
        response.sendRedirect(dest != null ? (String)dest : "/");
    }
}

출처:
코드로 배우는 스프링 웹 프로젝트 - 구멍가게 코딩단
https://elfinlas.github.io/2017/12/28/SpringBootInterceptor/

profile
Backend Developer
post-custom-banner

2개의 댓글

comment-user-thumbnail
2022년 8월 5일

안녕하세요~~ㅎㅎ 글 잘 읽었습니다. 이해도 잘되었구요!
AuthInterceptor에 prehandle쪽 if(session.getAttribute("login") != null) 부분에 !=이 오타이신거 같아 댓글남겨요!!

1개의 답글