Written by
Sunwoo Han
on
on
로그인 처리 - 필터
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술을 공부하고 정리하는 포스트입니다.
공통 관심사항
- 애플리케이션이 여러 로직에 공통적으로 관심이 있는 것 = 공통 관심사(cross-cutting concern)
- 일일이 처리할 경우 중복 코드가 많아짐
- 수정 시 모든 공통 로직을 수정해야 하므로 유지보수가 어려움
- ex) 로그인 해야만 사용할 수 있는 기능
- 스프링 AOP로 해결할 수 있음
- 웹과 관련된 공통 관심사는 서블릿 필터 또는 스프링 인터셉터 사용을 권장
- 이 두 가지는 웹과 관련된 다양한 부가 기능 제공 →
HttpServletRequest
제공
서블릿 필터
- 필터는 서블릿이 지원하는 수문장
필터 흐름
HTTP 요청 → WAS → 필터 → 서블릿 → 컨트롤러
- 필터를 적용하면 필터가 호출된 다음 서블릿이 호출
- 모든 고객의 요청 로그를 남기는 요구사항이 있다면 → 필터를 사용
- 스프링을 사용하는 경우 서블릿 → 스프링의
DispatcherServlet
필터 제한
HTTP 요청 → WAS → 필터 → 서블릿 → 컨트롤러 // 로그인 사용자
HTTP 요청 → WAS → 필터(적절하지 않은 요청이라 판단 → 서블릿 호출 X) // 비 로그인 사용자
- 필터에서 적절하지 않은 요청이라고 판단 → 로직을 종료
필터 체인
HTTP 요청 → WAS → 필터1 → 필터2 → 필터3 → 서블릿 → 컨트롤러
- 필터를 체인으로 구성
- 중간에 자유롭게 필터 추가 가능
필터 인터페이스
public interface Filter {
public default void init(FilterConfig filterConfig) throw ServletException { }
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException;
public default void destroy() { }
}
- 필터 인터페이스를 구현, 등록
- 서블릿 컨테이너가 필터를 싱글톤 객체로 생성 후 관리
init()
: 필터 초기화 메소드. 서블릿 컨테이너가 생성될 때 호출doFilter()
: 고객의 요청이 올 때 마다 호출. 필터 로직을 구현하는 메소드destroy()
: 필터 종료 메소드. 서블릿 컨테이너가 종료될 때 호출
서블릿 필터 - 요청 로그
LogFilter 로그 필터
@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 {
log.info("log filter doFilter");
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");
}
}
public class LogFilter implements Filter {}
- 필터를 사용하기 위해 필터 인터페이스 구현 필요
doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- HTTP 요청시 doFilter 호출
ServletRequest request
는 HTTP 요청이 아닌 경우까지 고려해서 만든 인터페이스- HTTP를 사용하면
HttpServletRequest httpRequest = (HttpServletRequest) request;
와 같이 다운 캐스팅을 사용해 진행
String uuid = UUID.randomUUID().toString();
- HTTP 요청을 구분하기 위해 요청당 임의의 uuid 생성
log.info("REQUEST [{}][{}]", uuid, requestURI);
- uuid, requestURI 출력
chain.doFilter(request, response);
- 필터에서 가장 중요한 부분
- 다음 필터가 있으면 필터를 호출하고, 필터가 없으면 서블릿을 호출
- 만약 이 로직을 호출하지 않으면 다음 단계로 진행되지 않음
WebConfig 필터 설정
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
- 스프링 부트를 사용 시
FilterRegistrationBean
사용해서 등록@ServletComponentScan
,@WebFilter
로도 필터 등록이 가능하지만 필터 순서는 조절할 수 없음.setFilter(new LogFilter())
: 등록할 필터 지정.setOrder(1)
: 체인으로 동작하는 필터의 순서 지정. 낮을수록 먼저 동작
addUrlPatterns("/*")
- 필터를 적용할 URL 패턴을 지정
- 한번에 여러 패턴을 지정 가능
- 필터를 등록할 때
urlPattern
을/*
로 등록했기 때문에 요청에 모든 해당 필터가 적용
📌 참고
- UUID를 사용하지 않고 실무에서 HTTP 요청 시
→ 같은 요청의 로그에 모두 같은 식별자를 자동으로 남기는 방법 :logback mdc
를 사용
서블릿 필터 - 인증 체크
LoginCheckFilter 인증 체크 필터
@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 = {"/", "/members/add", "/login", "/logout","/css/*"};
- 화이트 리스트를 제외한 나머지 모든 경로에는 인증 체크 로직을 적용
- 인증 필터를 적용해도 홈, 회원가입, 로그인 화면, css 같은 리소스에는 접근할 수 있어야 하기 때문
- 화이트 리스트 경로는 인증과 무관하게 항상 허용
isLoginCheckPath(requestURI)
- 화이트 리스트를 제외한 모든 경우에 인증 체크 로직을 적용
httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
- 미인증 사용자는 로그인 화면으로 리다이렉트
- 현재 요청한 경로인 requestURI를
/login
에 쿼리 파라미터로 함께 전달 - 이후
/login
컨트롤러에서 로그인 성공 시 해당 경로로 이동하는 기능은 추가로 개발
return;
- 필터 이후의 흐름을 진행하지 않기 위해 사용
- 필터는 물론 서블릿, 컨트롤러가 더는 호출되지 않음
- redirect를 사용했으므로 redirect가 응답으로 적용되고 요청 종료
WebConfig에 loginCheckFilter() 추가
@Bean
public FilterRegistrationBean loginCheckFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LoginCheckFilter());
filterRegistrationBean.setOrder(2);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
setFilter(new LoginCheckFilter())
- 로그인 필터를 등록
setOrder(2)
- 순서를 2번으로 적용
- 로그 필터 다음에 로그인 필터가 적용
addUrlpatterns("/*")
- 모든 요청에 로그인 필터를 적용
- 수정사항이 생길 경우 필터만 수정하는 방식
- 로그인 필터가 필요 없는 구간에서도 필터가 적용되는데, 이는 성능에는 큰 영향을 끼치지 않음
정리
- 서블릿 필터를 통해 로그인 하지 않은 사용자는 나머지 경로에 들어가지 못하도록 제한
- 공통 관심사를 서블릿 필터를 사용해서 해결
- 향후 로그인 관련 정책이 변경되어도 필터만 변경
Comments
SPRING 의 다른 글
-
스프링 타입 컨터버 24 Jun 2022
-
API 예외 처리 17 Jun 2022
-
예외 처리와 오류 페이지 12 Jun 2022
-
로그인 처리 - 인터셉터 08 Jun 2022
-
로그인 처리 - 필터 06 Jun 2022
-
로그인 처리 - 쿠키, 세션 31 May 2022
-
Bean Validation 22 May 2022
-
검증 22 May 2022
-
메시지, 국제화 21 May 2022
-
타임리프 - 스프링 통합과 폼 19 May 2022
-
타임리프 - 기본 기능 10 May 2022
-
스프링 MVC 기본 기능 - 웹 페이지 만들기 02 May 2022
-
스프링 MVC 기본 기능 - HTTP 응답 30 Apr 2022
-
스프링 MVC 기본 기능 - HTTP 요청 24 Apr 2022
-
스프링 MVC 기본 기능 - 요청 매핑 19 Apr 2022
-
스프링 MVC 기본 기능 19 Apr 2022
-
스프링 MVC 구조 이해 14 Apr 2022
-
MVC 프레임워크 만들기 - V4, V5 12 Apr 2022
-
MVC 프레임워크 만들기 - V1, V2, V3 09 Apr 2022
-
서블릿, JSP, MVC 패턴 05 Apr 2022
-
서블릿 29 Mar 2022
-
웹 애플리케이션 이해 24 Mar 2022
-
스프링 웹 계층이란? 05 Nov 2021
-
스프링 시큐리티 공식문서 번역 27 Sep 2021
-
스프링 AOP 총정리 : 개념, 프록시 기반 AOP, @AOP 27 Apr 2021
-
SpEL (스프링 Expression Language) 25 Apr 2021
-
데이터 바인딩 추상화 : Converter와 Formatter 21 Apr 2021
-
데이터 바인딩 추상화 : PropertyEditor 12 Apr 2021
-
Validation 추상화 10 Apr 2021
-
Resource 추상화 08 Apr 2021
-
IoC 컨테이너 9부 07 Apr 2021
-
IoC 컨테이너 8부 06 Apr 2021
-
IoC 컨테이너 7부 02 Apr 2021
-
IoC 컨테이너 6부 29 Mar 2021
-
IoC 컨테이너 5부 27 Mar 2021
-
IoC 컨테이너 4부 23 Mar 2021
-
IoC 컨테이너 3부 20 Mar 2021
-
IoC 컨테이너 2부 18 Mar 2021
-
IoC 컨테이너 1부 12 Mar 2021
-
스프링 PSA 07 Jan 2021
-
스프링 @AOP 실습 07 Jan 2021
-
프록시 패턴 06 Jan 2021
-
스프링 AOP 04 Jan 2021
-
의존성 주입(Dependency Injection) 04 Jan 2021
-
스프링 빈(Bean) 02 Jan 2021
-
스프링 IoC 컨테이너 01 Jan 2021
-
스프링 IoC 01 Jan 2021