로그인 처리 - 필터

byeol·2023년 3월 27일
0

김영한님의 mvc2 교재를 복습하며 정리합니다.
일단 수업 내용에 의존하지 않고 제 스스로 생긴 의문들을 바탕으로
정리하려고 노력하였습니다.


구조 분석

마찬가지로 수업을 배울 때 항상 실무자들은 어떻게 구조를 만드는지
눈여겨 보려고 노력합니다. 오늘 배운 부분도 구조를 분석합니다.

이번 수업에서는 Filter와 interceptor에 대해서 배웠고 둘은 web 폴더 아래 생성되어져 있습니다.
또한 ArgumentResolver 활용에 대해서 배웠습니다.


로그인한 사람만 들어갈 수 있는 페이지의 url

이를 로그인하지 않은 사람이 접속해서는 안된다.
따라서 로그인 여부를 체크하는 로직이 몇 십개의 페이지에 추가되어야 한다. 만약에 로그인 여부를 체크하는 로직을 수정해야할 때 몇 십개의 페이지를 들어가서 다 수정할 수 없는 노릇!

이와 같이 공통된 로직을 공통관심사라고 한다.

스프링의 AOP로도 해결할 수 있지만
웹과 관련된 공통 관심사는 서블릿 필터나 스프링의 인터셉터를 사용

웹과 관련된 공통 관심사를 처리할 때는 HTTP의 헤더나 URL의 정보들이 필요 -> 서블리 필터와 인터셉터는 HttpServletRequest 제공

서블핏 필터

필터는 서블릿이 지원하는 수문장

흐름
HTTP 요청 -> WAS -> 필터 -> 스프링의 디스패처 서블릿 -> 컨트롤러

필터에 특정 URL 패턴을 적용해서 어떤 URL은 통과, 미통과의 역할을 한다.

+) 디스패처 서블릿이란?
HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 프론트 컨트롤러(Front Controller)

필터 제한
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러 //로그인 사용자
HTTP 요청-> WAS -> 필터(적절하지 않은 요청 판단, 서블릿 호출 X)//비 로그인 사용자

필터 체인
HTTP 요청 -> WAS -> 필터1 -> 필터2 -> 필터3 -> 서블릿 -> 컨트롤러

필터 인터페이스



공식 문서에 말하는 Filter 인터페이스를 보자

필터는 리소스에 대한 요청(서블릿 또는 정적 콘텐츠)이나 리소스의 응답 또는 둘 모두에 대해 필터링 작업을 수행하는 개체입니다.

필터 인터페이스를 구현해서 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고 관리한다고 한다.

3가지의 메서드로 구성된다.

  • init () : 서블릿 컨테이너가 생성될 때 호출, 필터 초기화
  • doFilter() : 고객의 요청이 올 때 호출, 필터의 로직 구현
  • destory() : 서블릿 컨테이너가 종료될 때 호출, 필터 종료

필터 인터페이스를 구현한 서블릿 필터 만들기(로그)

처음 만드는 서블릿 필터는 요청 로그를 찍는 서블릿 필터이다.

package hello.login.web.filter;
...

@Slf4j
public class LogFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("log filter init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();

        String uuid = UUID.randomUUID().toString();

        try{
            log.info("REQUEST [{}][{}]",uuid, requestURI);
            chain.doFilter(request,response);
        }catch(Exception e){
            throw e;
        }finally {
            log.info("RESPONSE [{}][{}]",uuid,requestURI);
        }

    }
    @Override
    public void destroy() {
        log.info("log filter destroy");
    }
}

처음 서블릿 컨테이너가 생성될 때 "log filter init"
요청이 들어올 때마다 "REQUEST [{}][{}]", "RESPONSE [{}][{}]"
서블릿 컨테이너가 종료될 때 "log filter destroy"

FilterChain chain을 매개변수로 받는다.
FilterChain에 대한 공식문서의 설명은 아래와 같다.

FilterChain은 서블릿 컨테이너가 리소스에 대한 필터링된 요청의 호출 체인을 볼 수 있도록 개발자에게 제공하는 개체입니다. 필터는 필터 체인을 사용하여 체인의 다음 필터를 호출하거나 호출 필터가 체인의 마지막 필터인 경우 체인 끝에 있는 리소스를 호출합니다.

메서드도 단 하나이다.

필터 설정하기 WebConfig

...
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public FilterRegistrationBean logFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean =
                new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LogFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }
    
 }

스프링 부트에서 필터를 등록할 때는 FilterResgistrationBean을 사용해서 등록한다.

  • setFilter(new LogFilter())를 통해서 필터 지정
  • setOrder(1): 를 통해서 체인 순서 지정
  • addUrlPatterns("/*")를 통해서 필터를 적용할 URL 패턴 지정

+) @ServletComponentScan과 @WebFilter(filterName="logFilter",urlPatterns="/*")로 필터 등록 가능하지만 필터 체인에 의한 순서 조절이 안된다.


필터 인터페이스를 구현한 서블릿 필터 만들기(로그인 되었는지 체크)

요구사항
추후에도 추가되는 페이지에 대해서 로그인된 사용자만 접근할 수 있도록

따라서 로그인하지 않은 사용자가 접속 가능한 URL패턴만 배열로 저장하고
여기에 속하지 않으면 로그인 체크를 하는 필터를 추가하도록 해보자

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

    }

    /**
     * 화이트 리스트의 경우 인증 체크 X
     */
    private boolean isLoginCheckPath(String requestURI){
        return !PatternMatchUtils.simpleMatch(whitelist, requestURI);
    }

}

httpResponse.sendRedirect("/login?redirectURL=" + requestURI); 이 부분은 미인증 사용자는 다시 login페이지로 가는 것이다.

필터 설정하기 WebConfig

   @Bean
    public FilterRegistrationBean loginCheckFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean
                = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LoginCheckFilter());
        filterRegistrationBean.setOrder(2);
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }
profile
꾸준하게 Ready, Set, Go!

0개의 댓글

관련 채용 정보