[스프링 MVC - 2편] 로그인 처리2 - 필터, 인터셉터

지현·2022년 1월 5일
0

스프링

목록 보기
28/32

서블릿 필터 - 소개

공통 관심사(cross-cutting concern)

  • 애플리케이션 여러 로직에서 공통으로 관심이 있는 있는 것
  • 스프링의 AOP로도 해결할 수 있지만, 웹과 관련된 공통 관심사는 서블릿 필터 또는 스프링 인터셉터를 사용하는 것이 좋음
  • 웹과 관련된 공통 관심사를 처리할 때는 HTTP의 헤더나 URL의 정보들이 필요한데, 서블릿 필터나 스프링 인터셉터는 HttpServletRequest를 제공, 웹과 관련된 부가적인 기능 제공

서블릿 필터

필터 흐름

  • HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러
  • 필터가 호출 된 다음에 서블릿이 호출
  • 필터는 특정 URL 패턴에 적용 가능
  • /* 이라고 하면 모든 요청에 필터가 적용

필터 제한

  • 필터에서 적절하지 않은 요청이라고 판단하면 거기에서 끝냄
  • HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러 //로그인 사용자
  • HTTP 요청 -> WAS -> 필터(적절하지 않은 요청이라 판단, 서블릿 호출X) //비 로그인 사용자

필터 체인

  • 필터는 체인으로 구성되는데, 중간에 필터를 자유롭게 추가 가능
  • HTTP 요청 -> WAS -> 필터1 -> 필터2 -> 필터3 -> 서블릿 -> 컨트롤러

필터 인터페이스

  • 필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고, 관리
  • doFilter(): 고객의 요청이 올 때 마다 해당 메서드가 호출, 필터의 로직을 구현하면 됨

서블릿 필터 - 요청 로그

필터 생성

  • 필터를 사용하려면 필터 인터페이스(javax.servlet)를 구현 implements Filter
  • HTTP 요청이 오면 doFilter 가 호출
    • HTTP를 사용하면 ServletRequest를 HttpServletRequest로 다운 캐스팅 하여 사용
  • chain.doFilter(request, response);
    • 다음 필터가 있으면 필터를 호출하고, 필터가 없으면 서블릿을 호출
    • 만약 이 로직을 호출하지 않으면 다음 단계로 진행되지 않음

필터 등록

  • 스프링 부트를 사용한다면 FilterRegistrationBean을 사용해서 등록 -> 스프링 부트가 알아서 서블릿 컨테이너 올릴때 같이 등록을 해줌
  • setFilter(new LogFilter()) : 등록할 필터를 지정
  • setOrder(1) : 필터의 순서 설정, 낮을 수록 먼저 동작
  • addUrlPatterns("/*") : 필터를 적용할 URL 패턴을 지정, 한번에 여러 패턴 지정 가능
@Configuration
public class WebConfig {
    @Bean
    public FilterRegistrationBean logFilter(){
        //필터를 사용하고 싶을 때 등록해서 사용
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LogFilter());
        filterRegistrationBean.setOrder(1);
        //순서
        filterRegistrationBean.addUrlPatterns("/*");
        //어떤 URL 패턴에 적용할 건지, /* 적용하면 모든 URL에 다 적용됨

        return filterRegistrationBean;
    }
}

서블릿 필터 - 인증 체크

LoginCheckFilter.java

if(isLoginCheckPath(requestURI)){
                log.info("인증 체크 로직 실행 {}",requestURI);
                HttpSession session = httpRequest.getSession(false);

                if(session==null
                	||session.getAttribute(SessionConst.LOGIN_MEMBER)==null){
                    //로그인이 되지 않았을 때
                    log.info("미인증 사용자 요청 {}",requestURI);

                    //로그인으로 redirect, 로그인 하면 원래 접속하려던 페이지로 
                    // 다시 돌아가게 하기위해 현재 페이지 정보를 넘김
                    httpResponse.sendRedirect("/login?redirectURL="+requestURI);
                    return; //여기가 중요, 미인증 사용자는 다음으로 진행하지 않고 끝!

                }
            }
  • whitelist = {"/", "/members/add", "/login", "/logout","/css/*"};
    • 화이트 리스트를 제외한 나머지 모든 경로에는 인증 체크 로직을 적용
  • httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
    • 미인증 사용자는 로그인 화면으로 리다이렉트하는데, 현재 요청한 경로인 requestURI 를 /login 에 쿼리 파라미터로 함께 전달하면 로그인 후에 바로 원래 요청하려던 경로로 이동하게 개발할 때 사용할 수 있음
    • 컨트롤러에서 해당 쿼리 파라미터를 받아서 리다이렉트할때 사용
  • return;
    • 필터를 더는 진행하지 않음
    • 이후 필터는 물론 서블릿, 컨트롤러가 더는 호출되지 않음

LoginController.java

@PostMapping("/login")
    public String loginV4(@Valid @ModelAttribute LoginForm form, 
    			  BindingResult bindingResult,
                          @RequestParam(defaultValue = "/") String redirectURL,
                          HttpServletRequest request){
		
        ...

        return "redirect:"+redirectURL;
    }

공통 관심사를 서블릿 필터를 사용해서 해결한 덕분에 향후 로그인 관련 정책이 변경되어도 필터만 변경하면 됨

스프링 인터셉터 - 소개

스프링 인터셉터도 서블릿 필터와 같이 웹과 관련된 공통 관심 사항을 효과적으로 해결할 수 있는 기술이지만 순서, 범위, 사용방법이 다르고 더 많은 기능을 제공, 더 좋음
특별히 필터를 꼭 사용해야 하는 상황이 아니라면 인터셉터를 사용하는 것이 더 편리

스프링 인터셉터 흐름

  • HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러
  • 스프링 인터셉터는 디스패처 서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에 호출
  • 스프링 인터셉터에도 URL 패턴을 적용할 수 있는데, 서블릿 URL 패턴과는 다르고, 매우 정밀하게 설정 가능

스프링 인터셉터 제한

  • HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러 //로그인 사용자
  • HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 (적절하지 않은 요청이라 판단, 컨트롤러 호출 X) // 비 로그인 사용자
  • 인터셉터에서 적절하지 않은 요청이라고 판단하면 거기에서 끝냄

스프링 인터셉터 체인

  • HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터1 -> 인터셉터2 -> 컨트롤러
  • 스프링 인터셉터는 체인으로 구성되는데, 중간에 인터셉터를 자유롭게 추가할 수 있음

스프링 인터셉터 인터페이스

  • 인터셉터는 컨트롤러 호출 전(preHandle), 호출 후(postHandle), 요청 완료 이후(afterCompletion)와 같이 단계적으로 잘 세분화

스프링 인터셉터 호출 흐름

  • preHandle : 컨트롤러 호출 전에 호출
    • preHandle의 응답값이 false 인 경우 나머지 인터셉터는 물론이고, 핸들러 어댑터도 호출되지 않음
  • postHandle : 컨트롤러 호출 후에 호출
    • 컨트롤러에서 예외가 발생하면 postHandle은 호출되지 않음
  • afterCompletion : 뷰가 렌더링 된 이후에 호출 (완전히 응답이 난 다음에 호출)
    • afterCompletion 은 항상 호출 > 예외가 발생해도 호출
    • 컨트롤러에서 예외가 발생한 경우 예외 정보(ex)를 포함해서 호출됨

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

인터셉터 생성
LogInterceptor.java

  • 인터셉터를 사용하려면 인터셉터 인터페이스 HandlerInterceptor를 구현
  • 서블릿 필터의 경우 지역변수로 해결이 가능하지만, 스프링 인터셉터는 호출 시점이 완전히 분리되어 있기 때문에 preHandle에서 지정한 값을 postHandle, afterCompletion에서 함께 사용하려면 request.setAttribute, request.getAttribute 사용
    • 전역변수로 사용할 수 없는 이유는 싱글톤이여서 변수 사용하면 안됨
  • 핸들러, ModelAndView, 예외 등 필터보다 더 많은 정보를 받을 수 있음
  • preHandle에서 return값이 true면 정상 호출 > 다음 인터셉터나 컨트롤러가 호출됨

인터셉터 등록

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**","/*.ico","/error");
        //excludePathPatterns > 이 경로는 제외
    }
  • WebMvcConfigurer가 제공하는 addInterceptors()를 사용해서 인터셉터를 등록
  • registry.addInterceptor(new LogInterceptor()) : 인터셉터를 등록
  • order(1) : 인터셉터의 호출 순서를 지정
  • addPathPatterns("/**") : 인터셉터를 적용할 URL 패턴을 지정
  • excludePathPatterns("/css/**", "/*.ico", "/error") : 인터셉터에서 제외할 패턴을 지정

스프링의 URL 경로

  • ? : 한 문자 일치 /pages/t?st.html
  • * : 경로(/) 안에서 0개 이상의 문자 일치 /resources/*.png
  • ** : 경로 끝까지 0개 이상의 경로(/) 일치 /resources/**

스프링 인터셉터 - 인증 체크

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
    				Object handler) throws Exception {
        String requestURI=request.getRequestURI();
        log.info("인증 체크 인터셉터 실행 {}",requestURI);

        HttpSession session=request.getSession();
        if(session==null||session.getAttribute(SessionConst.LOGIN_MEMBER)==null){
            log.info("미인증 사용자 요청");

            //로그인으로 redirect
            response.sendRedirect("/login?redirectURL="+requestURI);
            return false; //문제가 될 때 종료
        }

        return true;
    }
  • 필터에서 사용한 화이트 리스트를 제외한 나머지 모든 경로에는 인증 체크 로직을 적용하는 복잡한 로직을 사용할 필요 X > 인터셉터를 등록할 때 처리
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginCheckInterceptor())
                .order(2)
                .addPathPatterns("/**") //모든 경로에 대해서 적용하지만
                .excludePathPatterns("/","members/add","/login","/logout",
                        "/css/**","/*.ico","/error"); // 이경로들은 제외해라!
    }
    }
  • 패턴을 세밀하게 적용 할 수 있음 > 인터셉터의 장점

정리

  • 서블릿 필터와 스프링 인터셉터는 웹과 관련된 공통 관심사를 해결하기 위한 기술
  • 특별한 문제가 없다면 사용하기 편리한 인터셉터를 사용하는 것이 좋음

ArgumentResolver 활용

애너테이션을 만들어서 파라미터에 사용하려면 ArgumentResolver에서 인식하게 해야함

  1. 애너테이션 생성
  2. HandlerMethodArgumentResolver을 구현하는 LoginMemberArgumentResolver 생성
    • supportsParameter()로 ArgumentResolver가 사용되는 조건에 대해 작성
    • resolveArgument()supportsParameter()에서 true가 반환되면 실행되는 로직
  3. WebMvcConfigurer에 설정 추가


출처
[인프런] 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술

0개의 댓글