점프 투 스프링부트 3-05 파트에 스프링 시큐리티 내용이 나온다.
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE'
스프링 시큐리티는 인증과 권한에 대해 Filter 흐름에 따라 처리하고 있다. Filter에 대해 알아보고 추가로 Interceptor와 비교해보자.
필터는 디스패처 서블릿에 요청이 전달되기 전/후에 url 패턴에 맞는 모든 요청에 대해 부가적인 작업을 처리할 수 있다.
💡 디스패처 서블릿(In)
클라이언트에서 요청을 받게 되면 톰캣 같은 서블릿 컨테이너가 요청을 받게된다. 이 요청을 프론트 컨트롤러인 디스패처 서블릿이 받고 적합한 컨트롤러에게 위임해준다.
스프링 컨테이너가 아닌 톰캣과 같은 서블릿 컨테이너에 의해 관리되는 것이고 디스패처 서블릿 전/후에 처리하는 것이다.
필터는 기본적으로 스프링과 무관하게 전역적으로 처리해야 하는 작업들을 처리할 수 있다. 대표적으로 보안 공통 작업이 있다. 올바른 요청이 아닐 경우 차단하기 때문에 스프링 컨테이너까지 전달되지 못하기 때문에 안정성을 높일 수 있다. 또한 이미지나 데이터 압축, 문자 인코딩, 비밀번호 암호화 등 있다.
필터를 추가하기 위해서는 jakarta.servlet
의 Filter 인터페이스를 구현해야 하고 3가지 메서드를 가지고 있다.
import jakarta.servlet.*;
import java.io.IOException;
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
init()
init 메서드는 필터 객체를 초기화하고 서비스에 추가하기 위한 메서드이다. 서블릿 컨테이너는 init 메서드를 한 번 호출하여서 필터 객체를 초기화 한다. 이후 요청은 doFilter()
를 통해 처리된다.
doFilter()
doFilter 메서드는 디스패처 서블릿으로 전달되기 전 url 패턴에 맞는 모든 요청이 서블릿 컨테이너에 의해 실행되는 메서드이다.
destroy()
destroy 메서드는 필터 객체를 제거하고 자원을 반환한다. 서블릿 컨테이너에 의해 한 번 호출되며 doFilter에 의해 처리되지 않는다.
인터셉터는 Spring이 제공하는 기술로, 디스패처 서블릿이 컨트롤러를 호출하기 전/후에 요청과 응답을 참조하고 가공하는 기능을 제공한다.
필터는 서블릿 컨테이너에서 동작, 인터셉터는 스프링 컨텍스트에서 동작
*실제로 인터셉터는 컨트롤러로 요청을 보내지 않고 순서만 나타냈다.
디스패처 서블릿은 인터셉터가 등록되어 있다면 순차적을 인터셉터를 거쳐 컨트롤러를 실행되도록 한다.
실제 코드는 예제에서 알아보자.
필터와 인터셉터를 추가하여 localhost:8080/test
에 요청을 보내보자.
@RestController
public class MainController {
@GetMapping("/test")
public String test() {
log.info("3. /test controller 호출");
return "ok";
}
}
Filter를 스프링 IoC컨테이너에 등록한다. @Component
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Slf4j
@Component
public class FilterExample implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
log.info("1. filter 전처리, 요청 url = {}", httpServletRequest.getRequestURL());
chain.doFilter(httpServletRequest, httpServletResponse);
log.info("5. filter 후처리, 응답");
}
}
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Slf4j
public class InterceptorExample implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("2. interceptor 전처리, 요청된 url = {}", request.getRequestURL());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("4. interceptor 후처리");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
}
먼저 HandlerInterceptor를 구현한다.
**preHandle**
메서드는 interceptor 전처리를 한다. 컨트롤러에 접근하기 직전에 실행되는 메서드이다.**postHandle**
은 컨트롤러 후, View로 결과를 전달하기 전 실행되는 메서드이다.다음은 인터셉터가 작동할 수 있도록 빈으로 등록해 주어야한다.
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class ConfigExample implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new InterceptorExample())
.excludePathPatterns("/css/**", "/images/**");
}
}
WebMvcConfigurer의 addInterceptors 메서드를 구현하면된다.
**addInterceptor**
에 위에 만들어 둔 Interceptor를 등록해준다.**excludePathPatterns**
는 제외할 url pattern을 적어준다.서버를 실행하여 localhost:8080/test
를 호출해자
위에 있는 그림과 같이 필터 전처리
→ 인터셉터 전처리
→ controller
→ 인터셉터 후처리
→ 필터 후처리
순으로 실행되는 것을 알 수 있다.
필터 | 인터셉터 | |
---|---|---|
컨테이너 | 서블릿 컨테이너 | 스프링 컨테이너 |
스프링 예외처리 여부 | x | o |
Request/Response 객체 조작 여부 | o | x |
용도 | 1. 공통된 보안 및 인증/인가 작업 2. 모든 요청에 대한 로깅 3. 이미지/데이터 압축 문자열 인코딩 | 1. 세부적인 보안 및 인증/인가 작업 2. API 호출에 대한 로깅 3. Controller로 넘겨주는 데이터 가공 |
💡 Request/Response 객체 조작 여부
여기서 조작한다는 것은 일부 변경이 아니라 아예 다른 객체로 바꾼다는 의미이다.
인터셉터의preHandle
을 보면 리턴 타입이 boolean이기 때문에 바꿀 수 없지만 필터는doFilter
직접 Request/Response 객체를 넣을 수 있다.