(1) 로그인 처리2 - 필터

CJY·2023년 4월 8일
0

스프링

목록 보기
10/14

이전까지 로그인을 처리하는 방법으로 쿠키와 세션에 대해 알아봤다. 그런데 만약에 로그인한 사용자만 사용할 수 있는 페이지를 만들고 싶다면 해당하는 모든 페이지에서 세션을 확인하는 코드를 만들어야 할까? 만들고 싶은 페이지의 CRUD에 같은 코드를 붙이고 만약에 로그인 로직이 바뀌는 날에는 코드를 갈아엎는 수준이 된다.

애플리케이션에서 여러 로직이 갖는 하나의 관심사항을 공통 관심사(cross-cutting concern)이라고 한다. 지금 상황은 인증에 대해 관심이 있는것이다.

스프링의 AOP로 해결할 수 있지만 HTTP의 헤더나 URL의 정보들이 필요하다는 점에서 서블릿 필터나 스프링 인터넷ㅂ터를 사용하는 것이 좋다. 이 둘에게는 HttpServletRequest를 제공하기 때문이다.

필터

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

필터 흐름

HTTP 요청 \rarr WAS \rarr 필터 \rarr 서블릿 \rarr 컨트롤러

여기서 서블릿은 스프링의 디스패처 서블릿이다.
흐름에서 알 수 있듯 만약 고객의 모든 요청에 대한 로그를 남기고 싶다면 필터에서 수행하면 된다.

중요한건 디스패처 서블릿으로 가기 전에 필터를 거치는데 말 그대로 필터링할 수 있다. 대충 설명하자면 필터에 URL을 등록하고 그 URL에 대해 사용자 요청이 들어왔지만 로그인이 안된 사용자라면 그냥 거기서 끝이다.

필터는 체인구조로 여러개를 적용할 수 있다.

HTTP 요청 \rarr WAS \rarr 필터1 \rarr 필터2 \rarr 서블릿 \rarr 컨트롤러

예를 들어 로그를 남기는 필터 다음에 로그인을 거르는 필터를 체인처럼 적용할 수 있다.

필터 인터페이스

public interface Filter {
	public default void init(FilterConfig filterConfig) throws ServletException {}
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
	public default void destroy() {}
}

필터 인터페이스를 구현하면 서블릿 컨테이너가 생성 및 관리해준다. (싱글톤 객체)
init()과 destroy()는 서블릿 컨테이너가 생성되거나 종료될 때 호출하는 메서드이고 중요한건 doFilter()로 고객의 요청이 올 때마다 해당 메서드가 호출된다. 필터의 로직을 구현하면 된다.

doFilter()에 넘어오는 파라미터를 보면 HttpServletRequestHttpServletResponse가 아니다. ServletRequestServletResponse는 http를 포함한 상위 클래스이므로 HTTP에 대해 사용하고싶다면 다운 캐스팅해서 사용하면 된다.

그리고 doFilter()에서 항상 chain.doFilter(request, response)를 호출해줘야한다. 앞서 말했듯이 필터는 체인처럼 작용하므로 만약 이 로직을 호출하지 않으면 WAS자체가 멈추게 된다. 참고로 request, response는 자신이 받은 파라미터 외에 다른 값을 넣어줘도 된다.

필터 설정

필터를 등록하는 방법은 여러가지가 있다고 한다. 만약 스프링 부트를 사용한다면 FilterRegistrationBean을 사용해서 등록하면 된다.

@Configuration
public class WebConfig {

	@Bean
    public FilterRegistrationBean myFilter() {
        	FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
            filterRegistrationBean.setFilter(new 내구현체());
            filterRegistrationBean.setOrder(1);
            filterRegistrationBean.addUrlPatterns("/*");
            return filterRegistrationBean;
	}

setFilter()로 내가 만든 필터를 넣어주고
setOrder()로는 필터 체인에서의 순서를 지정해주고
addUrlPatterns에는 필터링하고싶은 URL을 넣어주면된다.

/*라고 해두면 모든 URL을 필터링하게 된다.

로그인 필터 구현 예시

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;
    	HttpServletResponse httpResponse = (HttpServletResponse) response;
    
    	String requestURI = request.getRequestURI();
    
        try {
            if (isLoginCheckPath(requestURI)) {
                HttpSession session = request.getSession(false);

                if (session == null || session.getAttribute("세션아이디이름") == null) {
                    httpResponse.sendRedirect("/login?redirectURL="+requestURI);

                    return; // 이거 중요!!
                }
            }

            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e;
        } finally {}
    }
 /**
 * 화이트 리스트의 경우 인증 체크X
 */
     private boolean isLoginCheckPath(String requestURI) {
        return !PatternMatchUtils.simpleMatch(whitelist, requestURI);
 	}

}
    

whitelist를 따로 만들었는데 눈치가 빠른 사람이라면 왜 만들었는지 이미 알아챘을 것 같다. 필터를 등록할 때 모든 URL에 적용하되 필터 자체에서 홈페이지나 회원가입화면, 로그인 화면, css관련 화면 등에서는 로그인하지 않은 사용자도 접근할 수 있도록 하기 위해 만든 것이다.

httpResponse.sendRedirect("/login?redirectURL=" + redirectURI) 이 부분은 사용자의 편의를 위해 개발자가 조금 더 코드를 만드는 부분이다. 사용자가 원하는 페이지에 접근했지만 로그인이 필요한 곳이라 로그인 화면으로 사용자를 리다이렉트 했을 때, 사용자가 로그인을 하고 나면 다시 그 페이지로 돌아오게 만드는 부분이다. 즉, 로그인을 바로한 사용자를 모두 홈페이지를 보내기보다는 상황에 따라 사용자를 기존 페이지로 보내줄 수 있다. 물론 /login 컨트롤러에서 로그인 성공시 쿼리 파라미터를 이용해 해당 경로로 이동하는 기능은 따로 추가해야한다.

try 안에 return이 중요한 포인트다. 필터에 걸러지기 때문에 사용자는 response를 통해 redirect 되지만 그 이후 필터나 서블릿을 통한 컨트롤러 접근은 막아진다. 반면 해당 필터를 통과한 사용자는 다음 필터나 디스패처 서블릿을 통해 컨트롤러에 도착한다.

Controller에서 redirect 처리

파라미터 중 하나를 @RequestParam(defaultValue = "/") String redirectURL로 해준다면 마지막에

return "redirect:"+redirectURL;

이런식으로 처리할 수 있다. redirect를 통해 들어오지 않은 사용자는 defaultValue로 인해 홈페이지로 이동하게된다.

정리

이제 우리가 원하는 방식으로 필요에 따라 URL을 통해 접속한 클라이언트는 인증을 하도록 리다이렉트할 수 있었다. 공통 관심사를 서블릿 필터를 사용해서 처리했기 때문에 추후에 로그인 처리에 대한 정책이 변경되어도 필터에서만 손을 보면 된다. 다음 시간에는 비슷하지만 다른 기능을 제공하는 스프링의 인터셉터에 대해 알아보자.

profile
열심히 성장 중인 백엔드

0개의 댓글