왜 푸른수염이냐고요? 제목 재미없게 쓰면 안들어와볼거잖아요 ㅠ
르세라핌의 콘서트가 열렸다. 콘서트에 입장하는 사람들과 권한을 어떻게 관리할까?
보안 검색대와 VIP 팬클럽
콘서트장 입구에는 강력한 보안팀이 존재한다.
먼저, 모든 입장객에 대해서 철저히 확인될 필요가 있다. 신분증 확인, 티켓 검사, 가방 체크 등 모든 사람은 입장 전에 이 과정을 통과해서 어떤 사용자인지 판별이 되어야 입장이 가능하다. 이처럼 필터는 모든 HTTP 요청에 대해서 작동한다. 인증, 인코딩 설정, CORS 처리 등 모든 요청을 대상으로 한다. 기본적인 자격을, 입장하는 모든 사용자들을 대상으로 진행하는 것이다.
콘서트 내부에서는, VIP 팬클럽 전용 구역이 존재한다.
해당 구역에 들어가려면 팬클럽 가입 여부와 얼마나의 등급인지를 확인해야 한다. 이 판별기의 역할을 하는 것이 바로 인터셉터이다. 모든 관객이 아니라, 팬클럽에 가입된 멤버만을 대상으로 확인한다. 이는 특정 요청이나 특정 컨트롤러에만 작동하는 인터셉터와 같다.
먼저 전용 구역에 입장하기 전에 판별(preHandle)해서, 팬클럽 멤버의 QR 코드 등으로 멤버 여부를 확인하고, 멤버가 맞다면 입장 허가를 내리고, 아니라면 일반 구역으로 이동하게 한다.
다음으로, 팬클럽 멤버임이 확인(postHandle)되면, 특별 기념품을 제공하거나 특별 전용 구역으로 입장을 허용한다.
마지막으로 콘서트가 끝난(afterCompletion) 후, 팬클럽 멤버의 만족도를 조사하고 기념품 수령 여부를 확인한다.
코드로 알아보기
먼저 Filter를 구현해보자.
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/*") // 모든 경로에 대해 필터 적용
public class SecurityFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 필터 초기화 로직 (필요 시)
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("모든 관객: 티켓 검사 중...");
// 요청을 다음 필터나 서블릿으로 전달
chain.doFilter(request, response);
System.out.println("모든 관객: 입장 확인 완료!");
}
@Override
public void destroy() {
// 필터 종료 로직 (필요 시)
}
}
모든 로직은 필터를 반드시 거치게된다.
다음으로 인터셉터의 코드이다.
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class FanClubInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String memberId = request.getHeader("Fan-Club-ID"); // 헤더에 팬클럽 ID가 있다고 가정
if (memberId != null && isValidFanClubMember(memberId)) {
System.out.println("팬클럽 멤버 확인됨: 특별 혜택 제공 준비 중...");
return true; // 컨트롤러로 요청 진행
} else {
System.out.println("팬클럽 미가입자: 일반 구역 입장으로 안내");
response.sendRedirect("/general-zone");
return false; // 요청 중단
}
}
private boolean isValidFanClubMember(String memberId) {
// 팬클럽 멤버 유효성 검사 로직 (데이터베이스 조회 등)
return "12345".equals(memberId); // 예시로 ID가 12345인 경우만 팬클럽 멤버로 판별
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("팬클럽 멤버 혜택 준비 완료 후 추가 작업 수행 중...");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("팬클럽 멤버의 공연 후 마무리 작업 중...");
}
}
참고. 앞으로 설명하면서 나올 DispatcherServlet 에 대한 설명은 이 글 제일 하단에 설명해놓았다.
필터는 전통적인 웹구조, 즉 JSP/Servlet 기반의 구조에서 주로 사용된다.
즉, Servlet 컨테이너(ex. Tomcat)를 통해 클라이언트의 HTTP 요청을 처리하는 것이 주 목적이다.
HTTP로 요청이 들어오면, 요청은 서블릿 컨테이너를 거쳐 서블릿이나 JSP로 전달된다. 서블릿이 요청을 처리하고 응답을 작성한다. 이때 응답은 다시 필터를 거쳐 클라이언트로 반환된다. 다음의 구조를 따르는 것이다.
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러 // 콘서트에 입장 가능한 사용자 |
HTTP 요청 -> WAS -> 필터(적절하지 않은 요청이라 판단, 서블릿 호출X) // 콘서트에 입장 불가능한 사용자 |
필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고, 관리한다.
- init(): 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출된다.
- doFilter(): 고객의 요청이 올 때 마다 해당 메서드가 호출된다. 필터의 로직을 구현하면 된다.
- destroy(): 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출된다.
인터셉터는 RESTful API의 클라이언트-서버 구조에서 주로 사용된다.
즉, JSON 또는 XML과 같은 형식으로 데이터를 교환하며, 무상태(stateless) 프로토콜을 따른다.
필터와 다르게, Spring MVC에서 주관하기 때문에, 서블릿 호출 다음에 호출된다. 다음과 같다.
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러 // 입장 허용 관객 |
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터(적절하지 않은 요청이라 판단, 컨트롤러 호출X) // 입장 불가 관객 |
서블릿 필터의 경우 단순히 request , response 만 제공했지만, 인터셉터는 어떤 컨트롤러( handler )가 호 출되는지 호출 정보도 받을 수 있다. 그리고 어떤 modelAndView 가 반환되는지 응답 정보도 받을 수 있다.
- preHandle : 컨트롤러 호출 전에 호출된다. (더 정확히는 핸들러 어댑터 호출 전에 호출된다.)
- postHandle : 컨트롤러 호출 후에 호출된다. (더 정확히는 핸들러 어댑터 호출 후에 호출된다.) (컨트롤러에서 예외가 발생하면 호출되지 않는다.)
- afterCompletion : 뷰가 렌더링 된 이후에 호출된다. (예외가 발생해도 항상 호출된다. 예외는 ex 파라미터로 받아서 어떤 예외가 발생했는지 로그로도 항상 출력할 수 있다. 예외가 발생하지 않았다면 ex 파라미터는 null이다.)
필터는 누가 관리하는가? : Servlet 컨테이너.
필터는 Java Servlet 스펙의 일부로, HttpServletRequest (http요청객체) 와 HttpServletResponse (http 응답객체) 를 다루는 Servlet 컨테이너 (ex. Tomcat, Jetty) 에 의해 실행된다.
즉, 웹 애플리케이션의 가장 앞단에서 작동하며, 요청이 DispatcherServlet이나 다른 서블릿에 도달하기 전에 실행된다. 필터는 서블릿 컨테이너가 http 요청을 받을 때 실행되며, 서블릿 컨테이너 레벨에서 http 요청과 응답을 가로챈다.
필터는 javax.servlet.Filter 인터페이스를 구현하여 사용할 수 있다.
인터셉터는 누가 관리하는가? : Spring MVC 라이브러리.
인터셉터는 Spring MVC 라이브러리에 의해 관리되고 실행된다. 이는 Spring의 DispatcherServlet이 요청을 처리할 때 실행되며, 컨트롤러로 요청이 전달되기 전(preHandle), 컨트롤러 처리 후(postHandle), 그리고 요청 완료 후(afterCompletion)에 동작한다.
Spring MVC의 컨텍스트 내에서 작동하므로, 요청이 DispatcherServlet에 의해 처리될 때만 실행된다.
org.springframework.web.servlet.HandlerInterceptor 인터페이스를 구현하여 사용할 수 있다.
DispatcherServlet?????
Spring Framework의 핵심 구성 요소 중 하나로, Spring MVC의 프론트 컨트롤러(Front Controller) 역할을 한다. 모든 HTTP 요청은 이 DispatcherServlet을 통해 들어오고, 이 DispatcherServlet이 요청을 적절한 핸들러(컨트롤러)로 전달하여 처리한다.
DispatcherServlet 의 HTTP 수문장으로써의 역할.
1. HTTP 요청 수신
2. 핸들러(컨트롤러) 매핑
3. 핸들러(컨트롤러) 실행
4. 뷰/응답 처리 및 반환
- 페이지 반환의 경우 : ModelAndView 객체를 통해 어떤 뷰(View)를 렌더링할지 결정하고, ViewResolver를 사용해 뷰를 찾아서 반환한다.
- RESTful API 의 경우 : 컨트롤러에서 반환된 데이터가 @ResponseBody 나 ResponseEntity를 통해 JSON 또는 XML 로 변환된다.(이때 메시지 컨버터(HttpMessageConverter)가 변환(직렬화)하는 역할을 한다.) 반환된 데이터를 응답 본문(data)에 포함시켜 클라이언트로 전송한다.
결론.
HTTP 요청을 처리하는 기본 웹 컴포넌트인 서블릿은, 필터와 인터셉터의 처리 이후에 비즈니스 로직을 수행한다.
필터는 웹 애플리케이션의 광범위한 요청/응답 처리에 사용되고, 인터셉터는 Spring MVC 컨트롤러 전후의 세밀한 처리를 위해 사용된다.
'Framework > Spring' 카테고리의 다른 글
[Servlet API] 웹 요청/응답 처리 도구 (2) | 2024.11.16 |
---|---|
Validation(유효성 검사), Entity가 아니라 DTO에서 한다는 사실 (3) | 2024.11.12 |
[Spring AOP] 빈 후 처리기, 프록시 위장 마법 (0) | 2024.11.01 |
[Spring AOP] Advice, Spring이 동적 프록시를 추상화하는 방법 (0) | 2024.10.27 |
[Spring AOP] ProxyFactory, 프록시 생성 공장 (2) | 2024.10.27 |