Spring MVC2 - 로그인 처리2

희운·2025년 5월 27일

SpringBoot

목록 보기
9/10

공통 관심 사항

만약에 특정 요청은 로그인한 사용자만 가능하도록 하고 싶을때 어떻게 해야할까

모든 컨트롤러에 로그인 여부를 체크하는 로직을 하나하나 작성하면 되겠지만, 이것도 너무 번거로운 일이다. 향 후 로그인 처리 로직이 바뀌면 모든 컨트롤러에 코드를 전부 수정해야하는데 이것도 너무나 번거로운 일이다.

이렇게 애플리케이션 여러 로직에서 공통으로 관심이 있는 것을 공통 관심사라고 한다. 여기서는 동록, 수정, 삭제, 조회 등등 여러 로직에서 공통으로 인증에 대해서 관심을 가지고 있다.

웹과 관련된 공통 관심사는 서블릿 필터 또는 서블릿 인터셉터를 사용하는 것이 좋다.

필터 흐름

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러

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

필터를 적용하면 필터가 호출 된 다음에 서블릿이 호출된다. 그래서 만약 모든 고객의 요청 로그를 남기는 요구사항이 있다면 필터를 사용하면 된다.

필터 제한

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

필터 체인

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

필터는 체인으로 구성 , 중간에 자유롭게 필터 추가 가능


필터 인터페이스

public interface Filter {
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    default void destroy() {
    }
}

원래 나느 인터페이스의 모든 메서드는 무조건 @Override 해야한다고 생각했지만 default 가 존재하는 메서드는 @Override 를 안해도 compile 오류가 발생하지 않는다.

하지만 여기서 중요한것이 doFilter() 메서드이다.

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

필터가 정말 수문장 역할을 잘 하는지 확인하기 위해 가장 단순한 필터인, 모든 요청을 로그로 남기는 필터를 개발하고 적용해보자.



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

doFilter(ServletRequset request, ServletResponse response, FilterChain chain)

  • HTTP 요청이 오면 doFilter 가 호출된다.

  • ServletRequest 는 HTTP 요청이 아닌 경우까지 고려해서 만든 인터페이스이다. HTTP 를 사용하면 HttpServletRequset httpRequset = (HttpServletRequest) request; 와 같이 다운 케스팅 하면 된다.

  • String uuid = UUID.randomUUID().toString();
    HTTP 요청을 구분하기 위해서 요청당 임의의 uuid 를 생성해둔다.

  • request.getRequestURI()는 URI 만 딱 가져온다(문자열 형태로 가져온다)

  • chain.doFilter(request, response) 이 부분이 제일 중요한데, 다음 필터가 존재하면 필터를 호출하고, 필터가 없으면 서블릿을 호출한다. 만약 이. ㅗ직을 진행하지 못하면 다음 단계로 진행을 못해 ( 제일 중요한 메서드)



@Configuration
public class WebConfig {

    @Bean
    public FilterRegistrationBean logFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LogFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.addUrlPatterns("/*"); //모든 url 에 적용

        return filterRegistrationBean;
    }

필터를 만들었으면 이제 필터를 등록을 해야한다.
스프링부트를 사용한다면 FilterRegistrationBean 을 사용해서 등록 하면된다.

  • setFilter(new LogFilter()): 등록할 필터를 지정한다.
  • setOrder(1) : 필터는 체인으로 동작한다. 따라서 순서가 필요하다. 낮을 수록 먼저 동작
  • addUrlPatterns("/*"): 필터를 적용할 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);
                    //로그인으로 redirect
                    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);
    }
  • whitelist = {"/", "/member/add", "/login", "/logout", "/css/*"};
    인증 필터를 적용해도 홈, 회원가입, 로그인, css 와 같은 리소스에는 접근할 수 있어야 한다. 이렇게 화이트 리스트 경로는 인증과 무관하게 항상 허용. 화이트 리스트를 제외한 나머지 모든 경로에는 인증 체크 로직을 적용한다.
  • isLoginCheckPath(requestURI)
    화이트 리스트를 제외한 모드 경우에 인증 체크 로직을 적용한다.
  • httpResponse.sendRedirect("/login?redirectURL= + requestURI)
    미인증 사용자는 로그인 화면으로 리다이렉트 한다. 그런데 로그인 이후에 다시 호믕로 이동해버리면, 원하는 경로를 다시 찾아가야 하는 불편함 존재.
  • return : 여기가 중요. 필터를 더는 진행 안한다. 왜냐하면 로그인이 안된 사용자라고 걸렀기 때문에 이전 필터로 가던가 이 전으로 돌아간다.
    여기서는 redirect 를 사용했기 때문에 redirect 가 응답으로 적용되고 요청이 끝난다.

항상 필터를 만들었으면 필터를 빈으로 등록해야 한다.


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

addUrlPatterns : 모든 요청에 로그인 필터를 적용 (어차피 안에서 거를것이다 )

profile
기록하는 공간

0개의 댓글