framework/spring

스프링 mvc2 - 5. 로그인 처리2 - 필터, 인터셉터

wooweee 2023. 5. 7. 01:59
728x90

사용 목적

  • 필터, 인터셉터
    • 동일 기능 제공
    • 필터 : 서블릿이 제공
    • 인터셉터 : 스프링이 제공

 

  • 공통 관심 사항 : 로그인 하지 않은 사용자도 URL을 호출하면 상품 관리 화면에 들어갈 수 있게 된다.

  • 해결방안
    1. 컨트롤러에서 로그인 여부를 체크하는 로직을 하나씩 작성
      -> 등록, 수정, 삭제 , 조회 등 동일 로직을 작성하는 부분들이 너무 많다.
      -> 로그인 관련 로직이 변경시, 작성한 모든 로직 다 수정 필요

    2. 웹과 관련된 공통관심사는 서블릿 필터 or 스프링 인터셉터 사용하는 것이 유리

 

 

1. 서블릿 필터

 

  • 서블릿 필터 흐름
    • Http 요청 -> WAS(톰캣서버) -> 필터1 -> 필터2 -> 필터3 -> 서블릿 -> 컨트롤러
      * 역순으로 돌아가면서 응답을 보낸다.

    • 필터는 필터링 한다는 의미도 가지지만 서블릿으로 가기전에 필터가 적용한 특정 url의 조건에 따라서 개발자가 원하는 공통로직을 수행하는 것

  • 사용 예
    • 로그인 한 사람만 보여지는 화면은 필터 제한을 통해서 해결을 할 수 있다.
    • HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러 //로그인 사용자
    • HTTP 요청 -> WAS -> 필터(적절하지 않은 요청이라 판단, 서블릿 호출X) // 비 로그인 사용자

 

  • 서블릿 필터 인터페이스
public interface Filter {
    public default void init(FilterConfig filterConfig) throws ServletException {}
    
    // doFilter가 핵심
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;
    
    public default void destroy() {} 

}

 

  • method()
    • 구현 후 bean에 등록하면 servlet 컨테이너가 필터를 싱글톤 객체로 생성하고 관리한다. 
    • init() : 필터 초기화 메서드 - 서블릿 컨테이너가 생성될 때 호출
    • doFilter(): 필터의 로직 구현부분 - 내가 원하는 공통로직을 실행하는 코드를 구현하면 된다.
    • destroy(): 필터 종료 메서드

 

1.1. servlet Filter - 요청로그

 

  • LogFilter
package hello.login.web.filter;

@Slf4j
public class LogFilter implements Filter { // Filter 구현 -> servlet 것이므로 import 주의


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    // servlet 시작되는 초기에 수행
        log.info("log filter init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("log filter doFilter");

        HttpServletRequest httpRequest = (HttpServletRequest) request; // ServletRequest로 된 이유는 HTTP 요청이 아닌 경우도 고려한 것. 그래서 dowmCasting 수행==형변환
        String requestURI = httpRequest.getRequestURI(); // 요청 uri를 string으로 받음
        String uuid = UUID.randomUUID().toString(); // 요청 마다 구분을 하기 위해서 uuid 생성

        // try catch 내용이 중요
        try{
            log.info("REQUEST [{}] [{}]", uuid, requestURI);
            chain.doFilter(request, response);
            // 가장 중요 -> 다음 필터가 존재시 다음 필터 수행, 없으면 servlet으로 넘어감 
            // 이거 없으면 해당 filter 작업 후 그냥 프로그램 종료
            // chain.doFilter 이후 logic 작성시, controller 호출 후, 뒷단에서 공통 logic 수행함을 의미

        }catch (Exception e){
            throw e;
        } finally {
            log.info("RESPONSE [{}] [{}]", uuid, requestURI); // servlet까지 동작 완료 되면 finally 수행후 종료
        }
    }

    @Override
    public void destroy() {
        log.info("log filter destroy"); // 잘 없어졌는지 확인하는 로그
    }
}

 

  • 구현 summary
    1. Filter implements 하고 추상메서드 오버라이딩

    2. init() : servlet이 실행되는 최초에 수행
    3. doFilter에서 내가 원하는 로직을 작성함

      1. HttpServletRequest으로 형변환

      2. 원하는 데이터 사용하고 필요 로직 작성
        • uuid와 같이 한번에 여로 요청이 올 때 누군지 구별 필요해서 작성
        • filter가 singleton이기 때문에 지역변수로 uuid를 넣어줘야한다.
      3. try-catch-finally 작성
        1. try
          • chain.doFilter(request, response); // 다음 필터 호출 없으면 servlet 호출
          • chain.doFilter기준으로 이전은 servlet 앞단 공통 logic 이후는 servlet 뒷단 공통 logic
        2. catch
          • try 내부 logic으로 부터 넘어온 예외를 받는다. - controller 예외도 다 받을 수 있다.
          • 받은 예외를 던지면 최종적으로 was까지 넘어가게 되어서 was에서 예외를 처리한다.
        3. finally
          • 상황마다 다른데 해당 로그가 끝난 곳 위치를 확인하기 위해 작성 

      4. destroy()
        • servlet이 종료(앱이 종료될 때) 시 수행
        • filter에서 사용한 자원 등을 반납, 종료하는 기능 수행

 

 

1.2. sublet Filter 등록

  • filter를 구현했다고 사용할 수 있는게 아니다.
  • 해당 filter를 container에 등록해야 적용
    1. FilterRegistrationBean : 권장
    2. @ServletComponentScan, @WebFilter(filterName=String, urlPatterns = String) : filter 순서 보장 안함

 

  • 참고: WebConfigFilterRegistrationBean  vs  FilterRegistrationBean
더보기

WebConfigFilterRegistrationBean은 Spring Boot에서 제공하는 특정한 클래스로, FilterRegistrationBean을 확장하여 추가적인 설정을 할 수 있게끔 해줍니다. WebConfigFilterRegistrationBean은 주로 Spring Boot의 application.properties나 application.yml과 같은 외부 설정 파일에서 필터 설정을 관리할 때 사용됩니다.

반면에 FilterRegistrationBean은 일반적인 Servlet Filter를 등록하는 데 사용되는 클래스입니다. 이 클래스를 사용하면 Servlet Filter를 등록하고 필터 매핑을 구성할 수 있습니다. FilterRegistrationBean은 Spring Boot에서도 사용 가능하며, 일반적인 Spring 프레임워크에서도 사용할 수 있습니다.

 

 

1.2.1. FilterRegistrationBean

    필터 등록 방법이 여러가지가 있지만 스프링 부트 사용시 FilterRegistrationBean을 사용

 

  • 등록 방법
    1. 필터 객체 셋팅
    2. 필터 순서 설정
    3. 어떤 URL일 때 적용할 지 URI 패턴 작성(복수 가능)
package hello.login;

// web/filter 에서 생성한 필터 class를 사용하기 위해서는 spring container에 등록을 해야한다.

@Configuration
public class WebConfig  {

    @Bean // bean 등록. filter 등록 방법은 여러가지 존재, springboot 사용시 FilterRegistrationBean 이용 추천
    public FilterRegistrationBean logFilter(){

        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new LogFilter()); // 구현한 web/filter/LogFilter 객체 생성
        filterFilterRegistrationBean.setOrder(1); // filter chain을 고려해서 순서 넣기
        filterFilterRegistrationBean.addUrlPatterns("/*"); //patterns 복수형 -> 패턴 여러개 기입 가능, /* : 모든 경로 의미

        return filterFilterRegistrationBean; // 작성한 filterFilterRegistrationBean 객체 반환
    }
}

 

 

1.2.2. @ServletComponentScan, @WebFilter(filterName=String, urlPatterns = String)

    자동 필터 설정 방법

  • 장점 : WebConfig 만들지 않아도 된다.
  • 단점 : 필터 여러개일 때 순서 적용이 안되서 권장하지 않음


  • 등록방법
    1. SpringApplication에 @ServletComponentScan 등록
    2. LogFilter에 @WebFilter(filterName=String, urlPatterns = String)

  • @ServletComponentScan
package hello.login;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@ServletComponentScan
@SpringBootApplication
public class ItemServiceApplication {

   public static void main(String[] args) {
      SpringApplication.run(ItemServiceApplication.class, args);
   }

}

 

  • @WebFilter(filterName=String, urlPatterns = String)
import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.UUID;

@Slf4j
@WebFilter(filterName = "logFilter", urlPatterns = "/*")
public class LogFilter implements Filter { // Filter 구현 -> servlet 것이므로 import 주의
... 동일

 

  • 참고
    • 실무에서 HTTP 요청시 같은 요청의 log에 모든 같은 식별자를 자동으로 남기는 방법으로 logback mdc 를 이용하므로 한번 검색하는 것을 추천한다.

 

 

1.3. 인증 필터

 

  • filter 구현
package hello.login.web.filter;

@Slf4j
public class LoginCheckFilter implements Filter {

    private static final String[] whitelist = {"/", "/members/add", "/login", "/logout",}; // "/css/*"
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        // 경로받기 -> 경로를 쓸데가 많기도하고 error 위치 잡기도 쉽다.
        String requestURI = httpRequest.getRequestURI();

        try {
            log.info("인증 체크 필터 시작 {}", requestURI);

            if (isLoginCheckPath(requestURI)) {
                log.info("인증 체크 로직 실행 {}",requestURI);
                HttpSession session = httpRequest.getSession(false); // session 생성 방지 false
                if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
                    // 없으면 null, 있어도 해당 value가 혹시나 null인 경우를 대비하는 것
                    log.info("미인증 사용자 요청 {}", requestURI);

                    // login 페이지로 redirect
                    // 아주아주 중요!!!!!!!!!! redirect시 login창으로 갈 때query문으로 아예 query문을 넣어서 보낸다.
                    // 아주아주 중요!!!!!!!!!! loginController에서 해당 query문을 사용한다.
                    httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
                    // redirectURL 퀴리문으로 requestURI를 들고 간다.

                    // 중요
                    return; // 미인증 사용자는 다음으로 진행을 못하고 return으로 끝난다.
                }
            }

            // whitelist에 포함된 경우 if문로직에 안들어가고 바로 chain으로 연결되서 다음으로 넘어감
            chain.doFilter(request, response);

        } catch (Exception e) {
            // 예외 로깅 해도 되는데 tomcat까지 보내주기 위해서 throw로 던짐
            throw e;

        } finally {
            log.info("인증 체크 필터 종료 {}", requestURI);
        }
    }

    /**
     * 화이트 리스트의 경우 인증 체크 X
     */
    private boolean isLoginCheckPath(String requestURI) {
        return !PatternMatchUtils.simpleMatch(whitelist, requestURI);
    }
}
  • redirct 활용
    • String requestURI = httpRequest.getRequestURI();
    • httpResponse.sedRedirect("login?redirectURL=" + requestURI)

 

  • filter 등록
package hello.login;

@Configuration
public class WebConfig  {

    @Bean 
    public FilterRegistrationBean logFilter(){

        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new LogFilter()); // 구현한 web/filter/LogFilter 객체 생성
        filterFilterRegistrationBean.setOrder(1); // filter chain을 고려해서 순서 넣기
        filterFilterRegistrationBean.addUrlPatterns("/*"); //patterns 복수형 -> 패턴 여러개 기입 가능, /* : 모든 경로 의미

        return filterFilterRegistrationBean; // 작성한 filterFilterRegistrationBean 객체 반환
    }

    @Bean
    public FilterRegistrationBean loginCheckFilter() {
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new LoginCheckFilter());
        filterFilterRegistrationBean.setOrder(2);
        filterFilterRegistrationBean.addUrlPatterns("/*"); // whitelist 걸어놔서 /* 해도 된다. - 효율적

        return filterFilterRegistrationBean;
    }
}

 

  • logincontroller
@PostMapping("/login")
public String loginV4(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult,
                      @RequestParam(defaultValue = "/") String redirectURL, 
                      // filter로부터 response에서 가져온 redirectURI를 @RequestParam으로 부터 가져온다.
                      HttpServletRequest request) {
    if (bindingResult.hasErrors()) {
        return "login/loginForm";
    }

    Member loginMember = longinService.login(form.getLoginId(), form.getPassword());

    if (loginMember == null){
        bindingResult.reject("loginFail", "id or pw 맞지 않습니다");
        return "login/loginForm";
    }

    // 로그인 성공 처리
    HttpSession session = request.getSession();
    session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember); 

    // 아주아주 중요!!!!!!!!!! redirect시에도 다시 직전 page로 넘어가는 경우
    return "redirect:" + redirectURL;
}
  • redirect 활용 방법
    • @RequestParam(defaultValue="/") String redirctURL

 

2. 스프링 인터셉터

 

2.1. 소개

  • 2개 비슷한데 spring intercepter가 더 많은 기능 제공

  • 스프링 인터셉터 흐름
    • Http 요청 - WAS - 필터 - 디스패처 서블릿 - 스프링 인터셉터 - 컨트롤러
      • 스프링 인터셉터는 디스패처 서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에 호출
        내부적으로 총 3곳에서 호출
      • URL 패턴 적용할 수 있는데, 서블릿 URL 패턴과는 다르고, 매우 정밀하게 설정할 수 있다.

  • 스프링 인터셉터 제한
    • Http 요청 - WAS - 필터 - 디스패처 서블릿 - 스프링 인터셉터 - 컨트롤러
    • Http 요청 - WAS - 필터 - 디스패처 서블릿 - 스프링 인터셉터(적절하지 않은 요청이라 판단시 컨트롤러 호출 안하고 멈춤)
      • 로그인 여부체크에 좋다.

  • 스프링 인터셉터 체인
    • HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터1 -> 인터셉터2 -> 컨트롤러

  • 결론
    • 스프링 인터셉터는 서블릿 필터보다 편리하고, 더 정교하고 다양한 기능을 지원

 

  • 스프링 인터셉터 인터페이스
public interface HandlerInterceptor {
    
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
                              Object handler) throws Exception {}
    
    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{}
}

 

  • 서블릿 필터와 스프링 인터셉터 차이점
    • 서블릿
      1. doFilter()만 제공
      2. request, response 제공
    • 인터셉터
      1. preHandle: 핸들러 어댑터 호출 전
      2. postHandle: 핸들러 어댑터 호출 후
      3. afterCompletion: 요청 완료 이후
      4. request, response, handler, modelAndView 제공

 

2.2. 동작 흐름

 

2.2.1. 정상 흐름

 

  1. preHandle
    • 컨트롤러 호출 전에 호출된다.
    • preHandle의 응답값이 true이면 다음으로 진행하고 false이면 더 진행하지 않는다.
  2. postHandle
    • 컨트롤러 호출 후에 호출
  3. afterCompletion
    • 뷰가 렌더링 된 이후에 호출

 

 

2.2.2. 스프링 인터셉터 예외 상황

 

  1. preHandle
    • 컨트롤러 호출 전에 호출
  2. postHandle
    • 컨트롤러에서 예외가 발생하면 postHandle은 호출되지 않는다.
  3. afterCompletion
    • afterCompletion은 항상 호출
    • 예외를 파라미터로 받아서 어떤 예외가 발생했는지 로그로 출력된다.

 

정리

  • 스프링 mvc 구조에 특화된 필터 기능을 제공
  • 필터를 꼭 사용해야 하는 상황이 아니면 인터셉터를 사용하는 것이 편리

 

 

2.3. 스프링 인터셉터 - 요청 로그

 

@Slf4j
public class LogInterceptor implements HandlerInterceptor {

    public static final String LOG_ID = "logId";
    // String uuid = ... -> 절대 이렇게 하지 말 것. singleTon 적용으로 인해서 문제 생김

    // controller 호출 전
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();

        String uuid = UUID.randomUUID().toString();
        // intercepter는 각 위치마다 호출 되는 것이 달라서 이와 같이 뭔가를 넘겨줄 때 request를 이용한다.
        request.setAttribute(LOG_ID, uuid); 

        // handler에는 다양한 것들이 들어갈 수 있는데 일반적으로 사용되는 2가지 요청의 정보를 받을 때 사용하는 2가지
        // @Controller, @RequestMapping: HandlerMethod
        // 정적 리소스: ResourceHttpRequestHandler
        if (handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler; // 호출할 controller 메서드의 모든 정보가 포함
        }

        log.info("request [{}][{}][{}]", uuid, requestURI, handler);
        return true;
    }

    // controller 호출 후
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle [{}]", modelAndView); // exception 발생시 멈추니깐 uuid 넣지를 않음
    }

    // view까지 보낸 후
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        String requestURI = request.getRequestURI();
        String uuid = (String)request.getAttribute(LOG_ID); // preHandle로부터 받는 uuid 값
        log.info("response [{}][{}]", uuid, requestURI);
        if (ex != null) {
            log.error("afterCompletion error!!", ex); // {} 생략 가능
        }
    }
}

 

요청로그 작성 - web/LogInterceptor

  • 요약 - preHandle
    1. String uuid : 요청 로그를 구분하기 위한 uuid

    2. request.setAttribute(LOG_ID, uuid)
      1. 서블릿 필터와 달리 호출 시점이 완전히 분리되어있기 때문에 향후 postHandle, afterCompletion에서 함께 사용하기 위해 request에 담아둔다.
      2. LogInterceptor도 싱글톤처럼 사용되기 때문에 class 영역에 멤버변수를 사용하면 위험

    3. Object handle
      1. HandlerMethod
        • 스프링 사용시 일반적으로 @Controller @RequestMapping을 사용하는데 이 경우 handler 정보로 HandlerMethod가 넘어온다.
      2. ResourceHttpRequestHandler
        • @Controller가 아닌 /resource/static같은 정적 리소스 호출 시 ResourceHttpRequestHandler가 핸들러 정보로 넘오기 때문에 해당타입 사용

    4. return
      • true; // 이래야 다음으로 넘어가진다.
      • false; // 동작이 중지된다.

 

  • 요약 - postHandle, afterCompletion
    1.  postHandle
      • modelAndView 정보를 알 수 있다.
      • 예외 발생시 작동자체를 안한다.
    2.  afterCompletion
      • 종료 로그 작성
      •  예외 발생시 해당 예외 받고 그대로 호출
      • preHandler에서 setAttribute로 날린 uuid를 종료 log에 찍을 때 사용
 

 

 

2.4. 스프링 인터셉터 등록 - login/webConfig

 

  • 스프링에서는 해당 interceptor를 등록하는 방법을 구현후 등록하는 방식으로 지정했다.

  • 등록 단계
    1. implements WebMvcConfigurer
    2. @Override addInterceptors
      1. addPathPatterns : interceptor에 포함
      2. excludePathPatterns : interceptor에 제외
@Configuration
public class WebConfig implements WebMvcConfigurer {

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

 

 

 

2.5. 로그인 인터셉터 작성 - loginCheckedInterceptor

  • excludePathPatterns로 체계적으로 제외할 목록들을 넣을 수 있어서 whitelist 생성 및 try-catch-finally 할 필요 없음

 

  • intercepter
@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;
    }
}

 

  • filter
package hello.login.web.filter;

@Slf4j
public class LoginCheckFilter implements Filter {

    private static final String[] whitelist = {"/", "/members/add", "/login", "/logout",}; // "/css/*"
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String requestURI = httpRequest.getRequestURI();

        try {
            log.info("인증 체크 필터 시작 {}", requestURI);

            if (isLoginCheckPath(requestURI)) {
                log.info("인증 체크 로직 실행 {}",requestURI);
                HttpSession session = httpRequest.getSession(false); // session 생성 방지 false
                if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
                    log.info("미인증 사용자 요청 {}", requestURI);

                    httpResponse.sendRedirect("/login?redirectURL=" + requestURI);

                    return; // 미인증 사용자는 다음으로 진행을 못하고 return으로 끝난다.
                }
            }

            chain.doFilter(request, response);

        } catch (Exception e) {
            throw e;
        } finally {
            log.info("인증 체크 필터 종료 {}", requestURI);
        }
    }

    private boolean isLoginCheckPath(String requestURI) {
        return !PatternMatchUtils.simpleMatch(whitelist, requestURI);
    }
}

 

2.6. 로그인 인터셉터 등록 - webconfig 설정

  • whitelist를 만들지 않는 이유는 webconfig 의 excludePathPatterns로 해결이 가능하기 때문
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LogInterceptor())
            .order(1)
            .addPathPatterns("/**") // 중요 - intercepter에 포함하는 부분
            .excludePathPatterns("/css/**", "/*.ico", "/error"); // 중요 - intercepter에 포함하지 않는 부분

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

}

 

3. ArgumentResolver

  • 6. 스프링 MVC - 기본기능 -> 요청 매핑 핸들러 어댑터 구조에서 ArgumentResolver 활용
  • 번외 내용 - 로그인 기능과 적절해서 추가적으로 작성

 

  • HomeController
public String homeLoginV3Spring(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model)
public String homeLoginV4Spring(@Login Member loginMember, Model model)

 

매번 로그인 기능을 생성할 때 @Session~~를 하는 것이 아니라 직접 만든 어노테이션(@Login)을 이용해서 조금 더 편리하게 작성을 할 때 사용한다.

 

  • Login 어노테이션 생성
    • @Target(ElementType.PARAMETER) : 파라미터에만 사용
    • @Retention(RetentionPolicy.RUNTIME) : reflection등을 활용할 수 있도록 런타임까지 어노테이션 정보 남긴다.
@Target(ElementType.PARAMETER) // params에서만 사용
@Retention(RetentionPolicy.RUNTIME) // runtime 까지 정보를 남김
public @interface Login{}

 

  • Login annotation을 ArgumentReslover 방식으로 구현하도록 하기
    • supportParameter() : @Login 어노테이션이 있으면서 Member type이면 해당 ArgumentResolver가 사용된다.
    • resolveArgument() : 컨트롤러 호출 직전에 호출 되어서 필요한 파라미터 정보를 생성해서 해당 params에게 준다.
@Slf4j
public class LoginMemverArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter){
        log.info("supportsParameter 실행") // 내부 cache 존재해서 동일 작업 반복시 code 수행안하고 cache로 수행
        
        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);
        // LoginController에서 session에 member 존재시 해당 member data를 member에 넣어준다.
}

 

  • WebMvcConfigurer에 등록해주기
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers){
        resolvers.add(new LoginMemberArgumentResolver());
    }
}

 

 

 

이전 발행글 : 스프링 mvc2 - 4. 로그인 처리1 - 쿠키, 세션

 

다음 발행글 : 스프링 mvc2 - 6. 예외 처리와 오류 페이지