[스프링 - MVC] 로그인처리 - 필터


서블릿 필터

개요

  • 이전 게시글에서 다룬 프로젝트에 다음 코드를 추가하자.
    • 뷰 템플릿 (userPage.html) - 기존 코드 수정

        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Title</title>
          <style>
            body {
              font-size: larger;
            }
          </style>
        </head>
        <body>
          <h1>사용자 페이지</h1>
          아이디: <span th:text="${user.id}">사용자 아이디</span> <br/>
          이름: <span th:text="${user.name}">사용자 이름</span> <br/>
      
        	<!-- 추가된 버튼 -->
          <button th:onclick="|location.href='@{/service}'|">서비스</button>
          <button th:onclick="|location.href='@{/member/logout}'|">로그아웃</button>
        </body>
        </html>
      
    • 뷰 템플릿 (serviceOnlyForUser.html)

        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Title</title>
        </head>
        <body>
          <h1>로그인한 사용자만 접근할 수 있는 페이지</h1>
        </body>
        </html>
      
    • 컨트롤러 (OnlyUserController.java)

        package prac.myPrac.controller;
      
        import ...
      
        @Controller
        @RequestMapping("/service")
        public class OnlyUserController {
      
            @GetMapping
            public String viewForUser(@ModelAttribute("user") User user) {
                return "serviceOnlyForUser";
            }
        }
      
  • 로그인된 사용자만 접근할 수 있는 서비스를 위해, 위와 같은 코드를 추가했다.
  • 하지만, 기대와는 달리 URL만 알고있다면 누구나 접근할 수 있다. (http://localhost/service)
  • 해당 문제를 수정해보자!


의도한 애플리케이션 흐름

  • 첫 접속

    첫 접속


  • 로그인

    로그인


  • 서비스 접근

    서비스 접근


목표

  • 로그인 한 사용자만 서비스 페이지로 넘어갈 수 있다.
  • 해결해야 하는 문제
    • 로그인을 하지 않아도, URL을 직접 입력하여 서비스 페이지로 접근할 수 있다.



서블릿 필터

서블릿 필터란?

  • 서블릿이 지원하는 수문장이다.


필터 흐름

HTTP요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러
  • 필터 적용시, 필터가 호출된 뒤 서블릿이 호출된다.
  • 참고: 스프링을 사용하는 경우, 서블릿==디스패처_서블릿


필터 제한

로그인O: HTTP요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러
로그인X: HTTP요청 -> WAS -> 필터 -> (비정상적인 요청이라고 판단, 서블릿 호출X)


필터 체인

HTTP요청 -> WAS -> 필터1 -> 필터2 -> ... -> 서블릿 -> 컨트롤러
  • 필터는 체인으로 구성된다.
  • 중간에 필터를 자유롭게 추가할 수 있다.



인증 체크 필터 구현

인증 체크 필터: LoginCheckFilter 클래스

import ...

public class LoginCheckFilter implements Filter {

    private static final String[] whitelist = {"/", "/member/add", "/member/login", "/member/logout"};

    /**
     * 실제 필터 로직 작성
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        try {
            if (isLoginCheckPath(httpRequest.getRequestURI())) {
                HttpSession session = httpRequest.getSession(false);
                if (session == null || session.getAttribute("loginUser") == null) {

                    /*
                    로그인 X일때 login 화면으로 redirect
                    다시 돌아올 url을 파라미터로 같이 넘겨줌
                     */
                    httpResponse.sendRedirect("/member/login?redirectURL=" +
                            httpRequest.getRequestURI());

                    return; //더이상 진행X
                }
            }
            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e;
        }

    }

    /**
     * 서블릿 컨테이너가 생성될 때 호출된다.
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    /**
     * 서블릿 컨테이너가 종료될 때 호출된다.
     */
    @Override
    public void destroy() {
        Filter.super.destroy();
    }

    /**
     * whitelist 요소에 포함된 url => false
     * whitelist 요소에 포함되지 않은 url => true
     */
    private boolean isLoginCheckPath(String requestUrl) {
        return !PatternMatchUtils.simpleMatch(whitelist, requestUrl);
    }
}
  • implements Filter
    • 필터를 구현할 때는, 해당 인터페이스를 구현해야한다.


  • init()
    • 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출된다.


  • doFilter()
    • 고객의 요청이 올 때마다, 해당 메서드가 호출된다.
    • 필터의 로직을 구현하면 된다.


  • destroy()
    • 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출된다.


  • whitelist = {"/", "/member/add", "/member/login", "/member/logout"}
    • 인증 필터를 적용해도, 해당 url에는 접근할 수 있어야한다.


  • isLoginCheckPath(requestURI)
    • 화이트 리스트를 제외한 모든 경우에 인증 체크 로직을 적용한다.


  • httpResponse.sendRedirect("/member/login?redirectURL=" + httpRequest.getRequestURI())
    • 미인증 사용자는 로그인 화면으로 리다이렉트한다.
    • 로그인 화면에서 로그인을 완료하면, 다시 원래 페이지로 돌아올 수 있다.
      • 쿼리 스트링으로 돌아올 URL을 로그인 컨트롤러에 전달했기 때문에


  • return;
    • 미인증 사용자가 컨트롤러를 호출할 수 없도록 막는다.
    • 앞서서 sendRedirect 를 사용했기 때문에, redirect가 응답으로 적용되고 요청이 끝난다.


필터 등록: WebConfig 클래스

import ...

@Configuration
public class WebConfig {
    
    @Bean
    public FilterRegistrationBean loginCheckFilter() {
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<Filter>();
        filterFilterRegistrationBean.setFilter(new LoginCheckFilter()); // 로그인 필터 등록
        filterFilterRegistrationBean.setOrder(1); // 필터 순서=1
        filterFilterRegistrationBean.addUrlPatterns("/*"); // 적용할 url
        
        return filterFilterRegistrationBean;
    }
}
  • setFilter(new LoginCheckFilter())
    • LoginCheckFilter 를 필터로 등록한다.
  • setOrder(1)
    • 필터 적용 순서이다.
    • 첫번째 순서로 호출된다.
  • addUrlPatterns("/*")
    • 해당 필터를 적용할 url을 설정한다.
    • /* 은 모든 url을 의미한다.


로그인 성공시 기존 url로 돌아오기: LoginController 클래스

@PostMapping("/login")
public String login(@Validated @ModelAttribute("user") LoginUserForm form,
                    BindingResult bindingResult,
                    @RequestParam(defaultValue = "/") String redirectURL ,
                    HttpServletRequest request) {

  //검증 오류시
  if (bindingResult.hasErrors()) {
      return "login";
  }

  //로그인 실행
  User user = loginService.login(form.getId(), form.getPassword());

  //로그인 실패시, 오브젝트 에러
  if (user == null) {
      bindingResult.reject("wrongUser", "아이디나 비밀번호를 확인하세요.");
      return "login";
  }

  //로그인 성공시

  //세션 생성 후, 값 추가
  HttpSession session = request.getSession();
  session.setAttribute(SessionConst.LOGIN_USER, user);

	//다시 돌아가기
  return "redirect:" + redirectURL;
}
  • @RequestParam(defaultValue = "/") String redirectURL
    • 해당 매개변수 이름을 갖는 쿼리 스트링을 받아온다.
    • defaultValue : 해당 이름이 없을 때의 기본 값


결과

  • 로그인X, 바로 URL(http://localhost:8080/service) 접근

    미인증시


  • 로그인 완료

    로그인 완료시




  • 본 게시글은 김영한님의 강의를 토대로 정리한 글입니다.
  • 더 자세한 내용을 알고 싶으신 분들이 계신다면, 해당 강의를 수강하시는 것을 추천드립니다.