작심십이일러의 스프링 시작하기(12)-2

서은경·2022년 9월 7일
0

Spring

목록 보기
22/43

이번 포스팅도 역시 실습 위주라 필요한 내용만 적겠다!

인터셉터 사용하기

인터셉터는 말그대로 사용자의 요청을 낚아채는 것이다! 인터셉터는 요청이 컨트롤러에 도달하기 전에 낚아채서 개발자가 원하는 추가적인 작업을 한후 핸들러로 보낼수 있도록 해준다.
예를 들어 로그인하지 않은 상태에서 비밀번호 변경 폼으로 갈 수 있다면? 당연히 로그인 폼으로 이동시켜줘야한다. 하지만 세상에 당연한 건 없기 때문에 인터셉터를 사용하여 처리해주자.

🙋‍♀️인터셉터를 사용하는 이유가 뭐죠? HttpSession에 authInfo 객체가 존재하는지 검사하고 존재하지 않으면 로그인 경로로 리다이렉트 하는 방법도 있지 않나요?

@GetMapping
public String form(@ModelAttribute("command") ChangePwdCommand pwdCmd, HttpSession session) {
	AuthInfo authInfo = (AuthInfo) session.getSession("authInfo");
	if(authInfo == null) {
    	return "redirect:/login"
    }
    return "edit/changePwdForm";
}

이렇게요!
💡실제 웹 어플리케이션에서는 특정 기능 외에 더 많은 기능에 로그인 여부를 확인해야 한다. 각 기능을 구현한 컨트롤러 코드마다 세션 확인코드를 삽입하는 것은 많은 중복을 일으킨다. 이렇게 다수의 컨트롤러에 대해 동일한 기능을 적용해야 할 때 사용할 수 있는 것이 HandlerInterceptor이다 !

HandlerInterceptor 인터페이스 구현하기

org.springframework.web.HandlerInterceptor 인터페이스를 사용하면 다음의 세 시점에 공통 기능을 넣을 수 있다.

  • 컨트롤러(핸들러) 실행 전
  • 컨트롤러(핸들러) 실행 후, 아직 뷰를 실행하기 전
  • 뷰를 실행한 이후

세 시점을 처리하기 위해 HandlerInterceptor 인터페이스는 다음 메서드를 정의하고 있다.


package org.springframework.web.servlet;

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

import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;

public interface HandlerInterceptor {

	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return true;
	}

	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}
    
	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}

}

prHandle() 메서드는 컨트롤러(핸들러) 객체를 실행하기 전에 필요한 기능을 구현할 때 사용한다. handler 파라미터는 웹 요청을 처리할 컨트롤러(핸들러) 객체이다.

이 메서드를 사용하면

  • 로그인하지 않은 경우 컨트롤러를 실행하지 않음
  • 컨트롤러를 실행하기 전에 컨트롤러에서 필요로 하는 정보를 생성

하는 작업이 가능하다.

postHandle() 메서드는 컨트롤러가 정상적으로 실행된 이후에 추가기능을 구현할 때 사용한다. 컨트롤러가 익셉션을 발생하면 postHandle() 메서드는 실행하지 않는다.

afterCompletion() 메서드는 뷰가 클라이언트에 응답을 전송한 뒤에 실행된다. 컨트롤러 실행 과정에서 익셉션이 발생하면 이 메서드의 네번 째 파라미터로 전달된다. 익셉션이 발생하지 않으면 네번째 파라미터는 null이 된다. 따라서 컨트롤러 실행 이후에 예기치않게 발생한 익셉션을 로그로 남긴다거나 실행 시간을 기록하는 등의 후처리를 하기에 적합한 메서드이다.

package interceptor;

import org.springframework.web.servlet.HandlerInterceptor;

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

public class AuthCheckInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object authInfo = session.getAttribute("authInfo");
            if (authInfo != null) {
                return true;
            }
        }
        response.sendRedirect(request.getContextPath() + "/login");
        return false;
    }
}

🙋‍♀️인터페이스 내에 default 메서드는 상속받지 않아도 되나요?
💡YES! 다만 메서드 앞에 default를 붙이고 구현부가 {} 꼭 존재해야 한다. 자바8부터 생겼으며 이 인터페이스를 상속받고 필요한 메서드만 재정의하면 된다.

HandlerInterceptor 설정하기

MvcConfig 설정 클래스에 HandlerInterceptor 관련 설정을 추가한다.

    // 인터셉터를 설정하는 메서드
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authCheckInterceptor()).addPathPatterns("/edit/**");
    }

    @Bean
    public AuthCheckInterceptor authCheckInterceptor() {
        return new AuthCheckInterceptor();
    }

컨트롤러에서 쿠키 사용하기

대다수의 사이트는 사용자 편의를 위해 아이디를 기억해 두었다가 다음에 로그인할 때 아이디를 자동으로 넣어준다. 이 기능을 구현할 때 쿠키를 사용한다.

	@GetMapping
    public String form(LoginCommand loginCommand, @CookieValue(value="REMEMBER", required=false) Cookie rCookie) {
        if(rCookie != null) {
            loginCommand.setEmail(rCookie.getValue());
            loginCommand.setRememberEmail(true);
        }
        return "login/loginForm";
    }
    
    @PostMapping
    public String submit(LoginCommand loginCommand, Errors errors, HttpSession session, HttpServletResponse response) {
        new LoginCommandValidator().validate(loginCommand, errors);
        if (errors.hasErrors()) {
            return "login/loginForm";
        }
        try {
            AuthInfo authInfo = authService.authenticate(loginCommand.getEmail(), loginCommand.getPassword());

            // HttpSession의 authInfo 속성에 인증정보 객체를 저장
            session.setAttribute("authInfo", authInfo);

            Cookie rememberCookie = new Cookie("REMEMBER", loginCommand.getEmail());
            rememberCookie.setPath("/");
            if (loginCommand.isRememberEmail()) {
                rememberCookie.setMaxAge(60 * 60 * 24 * 30);
            } else {
                rememberCookie.setMaxAge(0);
            }
            response.addCookie(rememberCookie);

            return "login/loginSuccess";
        } catch (WrongIdPasswordException e) {
            errors.reject("idPasswordNotMatching");
            return "login/loginForm";
        }
    }

@CookieValue 어노테이션을 사용하여 쿠키를 쿠키 파라미터로 전달받는다. @CookieValue 어노테이션의 value 속성은 쿠키의 이름을 지정한다.

0개의 댓글