로그인3 / 필터

강한친구·2022년 6월 21일
0

Spring

목록 보기
23/27

서블릿 필터

로그인 상태에서만 처리할 수 있는 기능들의 컨트롤러에는 전부 로그인 체크 여부를 집어넣어야한다.

하지만 이런 방식은 실수가 생길 가능성도 높아지며, 유지보수가 어려워진다.

이런 모든 컨트롤러가 관심을 가져야하는 사항을 공통 관심사라고 한다.
이런 공통관심사는 AOP를 통해서 처리할 수도 있지만, HttpServletRequest를 제공해주는 인터셉터를 사용하는것이 좋다.

필터 흐름

기본흐름은 다음과 같다. 
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러

여기서 필터링에 제한을 걸면 서블릿을 호출하지 않고 컨트롤러에 가지 않은상태로 끝낼 수 있다

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러 //로그인 사용자
HTTP 요청 -> WAS -> 필터(거부) //비 로그인 사용자

혹은 필터를 연속해서 사용하는 필터 체인을 만들 수 있다.
HTTP 요청 -> WAS -> 필터1 -> 필터2 -> 필터3 -> 서블릿 -> 컨트롤러

로그인 체크 필터

@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;
        String requestURI = httpRequest.getRequestURI();
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        try {
            log.info("인증 체크 필터 시작 {}", requestURI);
            if (isLoginCheckPath(requestURI)) {
                log.info("인증 체크 로직 실행 {}", requestURI);
                HttpSession session = httpRequest.getSession(false);
                if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
                    log.info("미인증 사용자 요청 {}", requestURI);
                    httpResponse.sendRedirect("/login?redirectURL=" +
                            requestURI);
                    return; 
                }
            }
            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e; 
        } finally {
            log.info("인증 체크 필터 종료 {}", requestURI);
        }
    }
    private boolean isLoginCheckPath(String requestURI) {
        return !PatternMatchUtils.simpleMatch(whitelist, requestURI);
    }
}
출처 : 인프런 김영한 스프링 강의

필터 인터페이스를 이용해서 로그인 필터를 구현하는 코드이다.

화이트리스트

해당 필터가 적용되어서는 안되는 url경로들이다.

isLoginCheckPath

로그인체크를 해야하는 uri인지 파악하는 메서드이다.

전체적 흐름

servletRequest를 받아서 httpServletRequest로 변환한다. 그 후, URI를 찾아내서 logincheck를 실행한다. 만약 체크를 해야하면 검증로직이 실행되는데 검증로직은 request에서 session을 가지고와서 session의 이름이 SessionConst.LOGIN_MEMBER인지 session이 있는지 확인하느것이다.

  1. 만약 미인증 사용자인 경우
    httpResponse에서 uri를 가지고와서 저장해두고 loginpage로 보낸다. 대신 uri 페이지 정보를 가지고 있기에 로그인에 성공하면 다시 돌려보내준다.

  2. 인증 사용자인 경우
    chainFilter로 다음 필터로 넘어간다. 없으면 그대로 끝이다.

스프링 인터셉터

스프링 인터셉터도 서블릿 필터와 같이 웹과 관련된 공통 관심 사항을 효과적으로 해결할 수 있는 기술이다.
서블릿 필터가 서블릿이 제공하는 기술이라면, 스프링 인터셉터는 스프링 MVC가 제공하는 기술이다.

인터셉터 흐름

기본흐름은 다음과 같다. 
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러

여기서 필터링에 제한을 걸면 서블릿을 호출하지 않고 컨트롤러에 가지 않은상태로 끝낼 수 있다

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러 //로그인 사용자
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 // 비 로그인 사용자

혹은 필터를 연속해서 사용하는 필터 체인을 만들 수 있다.
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터1 -> 인터셉터2 -> 컨트롤러

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

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 {
	}

}

스프링 인터셉터에서는 컨트롤러 이전에 호출되는 preHandle이 존재한다. preHandle의 응답값이 true면 진행하고, false면 진행하지 않는다.

컨트롤러가 호출 된 후, postHandle이 호출된다.

afterCompletion은 예외발생시에도 호출된다.

로그인 인터셉터

@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("denied");
            response.sendRedirect("login?requestURL=" + requestURI);
            return false;
        }
        return true;
    }

로그인 인터셉터는 비교적 간단한 코드를 가지고 있는데, 이는 화이트리스트 처리를 하지 않기 때문이다. 적용 여부는 webConfig에서 등록을 할 때 직접 작성해서 넣기 때문이다.

등록방법

	@Bean
    public FilterRegistrationBean loginCheckFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LoginCheckFilter());
        filterRegistrationBean.setOrder(2);
        filterRegistrationBean.addUrlPatterns("/*");

        return filterRegistrationBean;
    }
	@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");
    }

각각 필터, 인터셉터의 등록 방법이다.
보이는것처럼 인터셉터에서는 화이트리스트를 작성하지 않고 바로 등록하는것을 볼 수 있다.

ArgumentResolver

기존 MVC기본기능에서 RequestMappingHandler 구조에서 ArgumentResolver를 공부했었다. 해당 기능을 이용한 로그인 회원 검색이다.

0개의 댓글