서론
Spring Security를 활용해서 인증/인가하는 기능을 만들어보려고 합니다.
Spring Security 에서 구현중에 Filter와 Interceptor를 활용하여 해당 사항을 구현하고 있는데, Filter와 Interceptor의 차이점에 대해 알아보고 싶어 정리했습니다.
Filter와 Interceptor를 통해서 요청으로 들어온 URL을 구분하여 각 URL에 맞는 데이터를 원하는대로 처리한다거나
어떤 URL이 Response로 나올때 그 Response를 원하는대로 처리한다거나와 같은 공통적인 필터를 거치게하여 원하는 작업을 수행할 수 있습니다.
전체적인 데이터 처리 흐름 (Request, Filter, DispatcherServlet, Interceptor, ADOP, Controller)
우선 흐름에 대해 정리해보겠습니다.
그림을 보면,
- HttpServletRequest (Client의 요청) 이 전송됩니다. Servlet Context에 존재하는 Filter에 해당 Request가 전달됩니다.
- Filter를 거쳐서 Spring Context에 존재하는 DispatcherServlet에 전달됩니다.
- DispatcherServlet ( "/api/hello" 라는 URL로 데이터가 전송되었을때 DispatcherServlet이 Front Controller로써의 역할을 진행하여 공통적인 작업을 처리한 후 해당 요청에 맞는 API를 찾아서 Controller로 전달해줍니다.)을 거쳐서 Spring Context에 존재하는 Interceptor에 전달됩니다.
- Interceptor을 거쳐서 Spring Context에 존재하는 AOP에 전달됩니다.
- AOP를 거쳐서 Spring Context에 전달됩니다.
( 여기서 Servlet Context란 간단하게 정리하면, Java에서 Servlet Container와 Servlet이 통신하기 위해 사용되어지고 있는 메서드들을 가지고 있는 클래스가 Servlet Context 입니다. 즉, 웹 어플리케이션이 실행되어 (ex. 톰캣이 실행되었을때 ) Servlet Instance가 init() 메소드를 호출하여 Servlet Context, Servlet Container 들이 초기화됩니다. )
Filter란 무엇인가?
- 필터란 말그대로입니다. Filter함수를 거치면 원하는 데이터만 남고, 필요하지 않은 데이터는 자동으로 걸러지는 역할을 합니다. Filter는 애플리케이션의 보안, 로깅, 권한 부여, 예외 처리 등 다양한 측면에서 사용될 수 있습니다.
- Filter는 Servlet 컨테이너에서 동작하는 컴포넌트로, Servlet의 생명주기에 따라 실행됩니다.
- Filter는 주로 웹 애플리케이션의 전역적인 범위에서 동작하며, 모든 요청과 응답에 적용됩니다.
- 주요 기능으로는 요청의 분배, 요청 처리, 응답 생성, 생명주기 관리 등이 있습니다.
- Filter는 Servlet API에 의존하며, 네트워크 관련 작업에 초점을 맞추고, 일반적으로 서블릿 컨테이너의 Request-Response 파이프라인에서 동작합니다. 또한 Servlet Context에 존재하기에 Spring Context 내의 자원은 사용불가합니다.
예시로 들어보면, httpServletRequest가 전송되었을때 1차적으로 ServletContext에 존재하는 Filter가 해당 데이터를 먼저 마주칩니다. 이때, 개발자는 특정 URL 패턴에 맞는 모든 요청에 대해 부가작업을 처리할 수 있습니다. 예시로 들면 Spring Security에서는 "/logout" 이라는 URL이 들어온다면 로그아웃을 한다고 인식할 수 있습니다. 그러므로, 그 데이터를 곧바로 로그아웃 API로 이어지게 처리합니다.
Filter의 URL 패턴에 맞는 모든 요청을 처리하는것은 Spring Security 에서 Filter 처리에 매우 중요한 역할을 합니다.
Filter Interface 입니다.
Filter 를 구현해보면, 아래의 FIlter Summary는 Java 입니다. Spring은 다르게 Bean을 활용하여 다르게 등록합니다.
public class FilterExample implements Filter{
public void init(FilterConfig arg0) throws ServletException {
System.out.println("Filter 객체 존재하지 않을시 실행!");
System.out.println("그 이후부터는 바로 doFilter 함수 실행!");
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
System.out.println("Filter worked Before!");
chain.doFilter(req, resp);// 다음 필터처리
System.out.println("Filter worked After!");
}
public void destroy() {
}
}
- init(FilterConfig arg0)
- Filter 객체가 웹컨테이너의 ServletContext 에 존재하지 않을시 실행됩니다.
- 위의 방식은 Spring Boot에서는 저렇게 사용하지 않습니다. 위의 사용법과 약간의 차이가 존재하나 기본로직은 같습니다.
- doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
- Filter 역할을 진행하고, 다음 필터로 이동할 수 있게해주는 chain.doFilter(req, resp) 함수도 존재합니다.
Filter는 Spring Context에서 존재하지 않기에 Spring Context 내의 자원들을 사용할 수 없다는점을 기억합니다.
Interceptor란 무엇인가?
- Interceptor는 DispatcherServlet이 어떤 Controller로 데이터를 전달할지 정한 뒤 해당 Controller로 데이터를 전송했을때 해당 request를 인터셉트하여 먼저 데이터를 확인할 수 있습니다.
- Interceptor는 주로 웹 프레임워크에서 제공되는 기능으로, 주로 특정 URL 패턴이나 컨트롤러에 대해서만 적용할 수 있습니다.
- ㅇController로 넘겨주는 HttpServletRequest의 객체값을 가공할 수 있습니다.
- 아래 코드를 보면 알 수 있듯이, request객체의 데이터값자체를 변경하지는 못하지만, setAttribute를 통하여 데이터값을 수정할 수 있습니다.
- 예시로 JWT 토큰 값을 해석하여 해당 정보를 Controller에 보낼 수 있습니다.
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 요청 전처리 작업을 수행하는 로직을 구현합니다.
// 요청 파라미터의 값을 읽고 가공하는 예시
String username = request.getParameter("username");
if (username != null) {
// 요청 파라미터의 값을 가공하여 다른 형식으로 변환하거나 활용할 수 있음
String processedUsername = username.toUpperCase();
request.setAttribute("processedUsername", processedUsername);
}
return true; // 계속 진행
}
- Interceptor는 컨트롤러의 실행 전후에 추가 작업을 수행합니다. 주요 기능으로는 인증 확인, 로깅, 권한 부여 등이 있습니다.
- Interceptor는 주로 인터셉터 스택 형태로 관리되며, 등록된 순서대로 실행됩니다.
- Spring에서는 Spring MVC Interceptor를 사용하여 인터셉터를 구현하며, 컨트롤러 단위로 적용되어 세밀한 제어가 가능합니다.
1. Interceptor 인터페이스 구현
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// preHandle 메서드 - 컨트롤러 실행 전에 호출됨
// 요청 전처리 작업을 수행하는 로직을 구현합니다.
// 예: 인증 상태 확인, 권한 검사, 요청 파라미터의 유효성 검사 등
// 요청 파라미터의 값을 읽고 가공하는 예시
String username = request.getParameter("username");
if (username != null) {
// 요청 파라미터의 값을 가공하여 다른 형식으로 변환하거나 활용할 수 있음
String processedUsername = username.toUpperCase();
request.setAttribute("processedUsername", processedUsername);
}
return true; // 계속 진행
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// postHandle 메서드 - 컨트롤러 실행 후, 뷰가 렌더링 되기 전에 호출됨
// 추가 작업을 수행하는 로직을 구현합니다.
// 예: 모델 데이터 가공, 추가 데이터의 추가 등
ex)
// 모델 데이터 가공 예시
if (modelAndView != null) {
Map<String, Object> model = modelAndView.getModel();
// 모델 데이터 조작 또는 추가 작업 수행
model.put("extraData", "Additional data");
}
ex)
// 추가 작업 수행 예시
if (modelAndView != null) {
String viewName = modelAndView.getViewName();
// 특정 조건에 따라 리다이렉션 수행
if ("redirect:/dashboard".equals(viewName)) {
response.sendRedirect("/dashboard");
}
// 알림 메시지 설정 등의 작업 수행
modelAndView.addObject("message", "Task completed successfully!");
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// afterCompletion 메서드 - 뷰의 렌더링이 완료된 후에 호출됨
// 모든 작업이 완료된 후에 마무리 작업 등을 처리합니다.
}
}
- boolean preHandle
- return true 일시 계속진행이고, return false일 경우 중단됩니다.
- void postHandle
- void afterCompletion
- 리소스 해제와 같은 작업들이 실행될 수 있습니다.
- 오류 발생여부 등을 로그를 남긴다거나 할 수 있습니다.
2. Interceptor 등록
@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// Interceptor를 등록하는 메서드
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/secured/**") // 특정 URL 패턴에 대해서만 인터셉터를 적용
.excludePathPatterns("/public/**"); // 인터셉터 제외할 URL 패턴 설정 가능
}
}
마무리
위와 같이 Filter와 Servlet이 어떻게 사용되는지 알게 되었습니다.
다시 한번 정리해보면,
Filter는 Servlet 컨테이너에서 동작하면서, 주로 웹 애플리케이션의 전역적인 범위에서 동작합니다. 즉, 모든 요청과 응답에 적용됩니다. 또한 필터는 Request 객체와 Response 객체를 조작할 수 있습니다.
Interceptor는 Spring Context내에서 존재하면서, 특정 URL 패턴이나 컨트롤러에 대해서만 작동합니다. 좀 더 URL 특화적인 작업을 실행할때는 Interceptor를 사용합니다. Request 객체와 Response 객체를 조작할 수 없습니다. ( 속성은 변화 가능 ) 각 함수에 대해 알아보면, preHandle은 boolean 을 반환하여 interceptor를 중단시킬지 결정하고 있고, void postHandle, void afterCompletion은 주로 로깅처리나, 리소스 해제, Request와 Response의 속성수정과 같은 작업을 진행합니다. preHandle은 return 값이 boolean인것을 활용하여 사용자가 "USER"이냐 "ADMIN"이냐 권한에 따라서 InterCeptor라 컨트롤러로 계속해서 연결할지 안할지 검사하여 처리할 수 있는 기능을 해당 Interceptor로 구현한다거나, request.setAttribute("newAttribute", "helloworld") 와 같이 객체의 속성값을 수정하여 전송한다거나 같은 역할을 할 수 있습니다.
참고