본문 바로가기
[ JAVA ]/JAVA Spring

[ Spring ] Spring Interceptor - 인증 체크 / ArgumentResolver 활용

by 환이s 2024. 1. 15.

 

오늘은 서블릿 필터에서 사용했던 인증 체크 기능을 스프링 인터셉터로 개발해 보고 ArgumentResolver를 활용해 보겠습니다.


LoginCheckInterceptor

 

import hello.login.web.SessionConst;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {

     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
     
     String requestURI = request.getRequestURI();
     
     log.info("인증 체크 인터셉터 실행 {}", requestURI);
     
     HttpSession session = request.getSession(false);
     
     if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER)== null) {
        
        log.info("미인증 사용자 요청");
        
         //로그인으로 redirect
         response.sendRedirect("/login?redirectURL=" + requestURI);
    	 return false;
    	 }
     return true;
     }
}

 

 

서블릿 필터와 비교해서 코드가 매우 간결합니다.

인증이라는 것은 컨트롤러 호출 전에만 호출되면 되서 preHandle만 구현하면 됩니다.

 


webConfig

 

@Configuration
public class WebConfig implements WebMvcConfigurer {

     @Override
     public void addInterceptors(InterceptorRegistry registry) {
         
         registry.addInterceptor(new LogInterceptor())
                 .order(1)
                 .addPathPatterns("/**")
                 .excludePathPatterns("/css/**", "/*.ico", "/error");

         registry.addInterceptor(new LoginCheckInterceptor())
                 .order(2)
                 .addPathPatterns("/**")
                 .excludePathPatterns(
                     "/", "/members/add", "/login", "/logout",
                     "/css/**", "/*.ico", "/error"
         );
     }
     //...
}

 

인터셉터를 적용하거나 하지 않을 부분은 addPathPatternsexcludePathPatterns에 작상하면 됩니다.

 

기본적으로 모든 경로에 해당 인터셉터를 적용하되 (/**), 홈(/), 회원가입(/members/add), 로그인(/login), 리소스 조회(/css/**), 오류(/error)와 같은 부분은 로그인 체크 인터셉터를 적용하지 않습니다.

 


서블릿 필터와 스프링 인터셉터는 웹과 관련된 공통 관심사를 해결하기 위한 기술입니다.

 

서블릿 필터와 비교해서 스프링 인터셉터가 개발자 입장에서 훨씬 편리하다는 것을 코드로 이해가 될 거 같은데,

특별한 문제가 없다면 인터셉터를 사용하는 것이 좋을 거 같습니다.

 


ArgumentResolver 활용

 

ArgumentResolver는 어떠한 요청이 컨트롤러에 들어왔을 때, 요청에 들어온 값으로부터 원하는 객체를 만들어 내는 일을 간접적으로 해줄 수 있습니다.

 

예를 들어, JWT 토큰과 함께 요청이 들어왔다고 가정해보면 우리는 이 토큰이 유효한 토큰인지 검증을 거친 후에 토큰에 저장된 id를 꺼내서 로그인 유저 객체로 만들어 내는 과정이 필요합니다.

 

이러한 경우, ArgumentResolver를 사용하지 않는다면, 토큰을 검증하고 로그인 유저 객체로 변환하는 과정을 모든 컨트롤러마다 구현해야 합니다.

 

그러면 사용자 검증이 필요한 컨트롤러에 중복 코드가 생기고, 컨트롤러의 책임이 증가하게 됩니다. 

이러한 문제를 ArgumentResolver가 해결해 줄 수 있습니다.

 

예제를 간단한 로그인 회원을 편리하게 찾을 수 있는 코드로 알아봅시다.

 

[ @Login 애노테이션 생성 ]

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {
}

 

  • @Target(ElementType.PARAMETER) : 파라미터에만 사용한다.
  • @Retention(RetentionPolicy.RUNTIME) : 리플렉션 등을 활용할 수 있도록 런타임까지 애노테이션 정보가 남아있다.

[ HomeController - @Login 애노테이션 추가 ]

@GetMapping("/")
public String homeLoginArgumentResolver(@Login Member loginMember, Model model) {
        
        //세션에 회원 데이터가 없으면 home
         if (loginMember == null) {
         return "home";
         }
         
         //세션이 유지되면 로그인으로 이동
         model.addAttribute("member", loginMember);
         return "loginHome";
}

 

@Login 애노테이션이 있으면 직접 만든 ArgumentResolver가 동작해서 자동으로 세션에 있는 로그인 회원을 찾아주고,

만약 세션에 없다면 null을 반환하도록 했습니다.

 


[ LoginMemberArgumentResolver 생성 ]

import hello.login.domain.member.Member;
import hello.login.web.SessionConst;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@Slf4j
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {
     @Override
     public boolean supportsParameter(MethodParameter parameter) {
     
    	 log.info("supportsParameter 실행");
         
         boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class);
         
         boolean hasMemberType = Member.class.isAssignableFrom(parameter.getParameterType());
     
     return hasLoginAnnotation && hasMemberType;
     }
     
     @Override
     public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    
        log.info("resolveArgument 실행");
         HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest();
         HttpSession session = request.getSession(false);
         
         if (session == null) {
        	 return null;
         }
         
     return session.getAttribute(SessionConst.LOGIN_MEMBER);
     }
}

 

  • supportsParameter() : @Login 애노테이션이 있으면서 Member 타입이면 해당 ArgumentResolver가 사용됩니다.

 

  • resolveArgument() : 컨트롤러 호출 직전에 호출 되어서 필요한 파라미터 정보를 생성해 줍니다. 여기서는 세션에 있는 로그인 회원 정보인 member 객체를 찾아서 반환해 줍니다. 이후 SpringMVC는 컨트롤러의 메서드를 호출하면서 여기에서 반환된 member 객체를 파라미터에 전달해 줍니다.

[ WebMvcConfigurer에 설정 추가 ]

import hello.login.web.argumentresolver.LoginMemberArgumentResolver;
import hello.login.web.filter.LogFilter;
import hello.login.web.filter.LoginCheckFilter;
import hello.login.web.interceptor.LogInterceptor;
import hello.login.web.interceptor.LoginCheckInterceptor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.Filter;
import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {

     @Override
     public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
     	resolvers.add(new LoginMemberArgumentResolver());
     }
 //...
}

 

앞서 개발한 LoginMemberArgumentResolver를 등록합니다.

 

실행해 보면, 결과는 동일하지만, 더 편리하게 로그인 회원 정보를 조회할 수 있습니다.

이렇게 ArgumentResolver를 활용하면 공통 작업이 필요할 때 컨트롤러를 더욱 편리하게 사용할 수 있습니다.

 


마치며

 

오늘까지 로그인 처리할 때 필요한 Interceptor 포스팅을 마무리했습니다.

다음 포스팅에서 뵙겠습니다.

 

위 포스팅은 김영한님의 Spring MVC 2편 - 백엔드 웹 개발 활용  강의를 참고했습니다

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의 - 인프런

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

 

728x90