필터와 인터셉터

이누의 벨로그·2022년 10월 16일
0

스프링

목록 보기
1/1

출처: 인프런 강좌 - 스프링 MVC 2편

서블릿 필터

서블렛을 호출하기 전에 호출됨.

필터의 연속된 체인을 사용하거나 서블렛 호출을 막을 수도 있어서 로그인 여부등을 체크하기 좋음.

AOP와 비슷하다고 생각할 수 있으나 로그인 확인 등에 보다 적합함

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

인터페이스로 구현함. 싱글톤 객체로 스프링이 생성해줌(하나만 등록) 멀티쓰레드에서 조심할 것.

doFilter 메소드에서 chain.doFilter를 호출해야함.

WebConfig 클래스의 FilterRegistrationBean을 통해 필터를 등록해야함

@WebFilter 어노테이션으로도 필터 등록이 가능하나 순서를 설정할 수 없기 때문에 FilterRegistrationBean 사용을 권장.

urlPatterns는 특별히 등록하지 않고 doFilter 로직에서 화이트 리스트(통과할 url)만 빼도 됨

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException{
	HttpServletRequest httpRequest = (HttpServletRequest) request;
	HttpServletResponse httpResponse = (HttpServletResponse) response;
}

rediretURL 파라미터를 이용하면 로그인화면에서 리다이렉트시 원래 왔었던 URL을 보내어 추후에 다시 왔었던 곳으로 리다이렉트하도록 할 수 있음.

예:) 어떤 url에서 미로그인 사용자를 로그인 화면으로 리다이렉트 했을 시 로그인화면에서 로그인 성공하면 다시 원래 url로 돌아오도록.

이 후 로그인 컨트롤러에서 redirectURL 파라미터가 있을 경우 해당 url로 리다이렉트하는 로직을 구현

@PostMapping("/login")
public String login(@Valid @ModelAttribute LoginForm form,
 BindingResult bindingResult, 
HttpServletRequest request, 
@RequestParam(defaultValue="/") String redirectURL){
	return "redirect:" + redirectURL;//null이어도 홈으로 가기때문에 체크 필요없음  
}

FilterChain은 요청시 순서대로 한번 실행 되고, 요청이 종료될 시 다시 역순으로 한번 실행된다.(2번) 예:) 순서대로 1-2-3-2-1.

filter에서도 응답을 바로 리턴할 수 있으며 이때는 디스패치 서블릿을 호출하여 요청을 전달하지 않고 종료된다.

필터는 다음 체인에 ServletRequest나 ServletResponse를 직접 구현하여 넘길 수도 있음(참고)

서블릿 필터는 서블릿이 제공하는 기능

스프링 인터셉터는 스프링이 제공. 더 많은 기능을 제공

http 요청-> WAS -> 서블릿 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러

인터셉터 또한 체이닝이 가능하며, doFilter만 사용하는 필터와 달리

preHandle, postHandle, afterCompletion으로 세분화됨.

public interface HandlerInterceptor{
	default boolean preHandle(HttpServletRequest,HttpservletResponse, Object Handler)throws Exception{}
	default void postHandle(HttpServletRequest,HttpservletResponse, Object Handler, @Nullable ModelAndView, modelAndView)throws Exception{}
	default void afterCompletion(HttpServletRequest,HttpservletResponse, Object Handler, @Nullable Exception ex)throws Exception{}

다음과 같은 흐름을 가짐

afterCompletion은 finally와 같이 예외와 상관없이 항상 호출되며, preHandle은 컨트롤러에서 예외가 발생하기 전에 호출된다. 예외가 발생할 때 취소되는 것은 postHandle이다.

인터셉터는 보다시피 스프링 MVC에 특화되어 있으므로 필터가 꼭 필요한 상황이 아니라면 인터셉터를 사용하는 것이 편리하다.

PreHandle

@Override
public boolean preHandle(HttpServletRequest,HttpservletResponse, Object Handler)throws Exception{
	//@RequestMapping을 사용하는 경우 handler의 타입은 HandlerMethod
	String requestURI = request.getRequestURI();
	String uuid = UUID.randomUUID().toString();
	request.setAttribute("logId", uuid); //동일한 요청인지 로그를 찍어보기 위해 request에 전달함
	if(handler instanceOf HandlerMethod){
		HandlerMethod hm = (HandlerMethod) handler;//호출할 컨트롤러 메소드의 모든 정보를 가지고 있음
	}
	//정적 리소스를 사용하는 경우 타입은 ResourceHttpHandler 
	return true; //진행 여부를 boolean으로 리턴
}

PostHandle

@Override
public void afterCompletion(HttpServletRequest,HttpservletResponse, Object Handler, @Nullable Exception ex)throws Exception{
	String logId = (String) request.getAttribute("logId");
	if(ex!=null){
		log.error("after completion:", error);
	}
}	

예외와 상관없이 실행되기 때문에 Exception을 처리할 수 있다.

등록

다음과 같이 등록한다.

@Configuration
public class WebConfig implements WebMvcConfigurer{
	@Override
	public void addInterceptors(InterceptorRegistry registry){
		registry.addInterceptor(new LogInterceptor())
						.order(1)
						.addPathPatterns("/**")//서블릿과 다른 패턴을 사용하므로 주의, 하위 패턴들을 뜻함
						.excludePatterns("/css/**", "/*.ico", "/error"); //화이트 리스트와 같이 제외할 수 있음
	}
}

로그인 체크 인터셉터


@Override
private final String const LOGIN_MEMBER = "loginMember";
public boolean preHandle(HttpServletRequest,HttpservletResponse, Object Handler)throws Exception{
	String requestURI = request.getRequestURI();
	log.info("인증 체크 인터셉터 실행:{}", requestURI);
	HttpSession session = request.getSession(false);
	if(session==null || session.getAttribute(SessionConst.LOGIN_MEMBER==null){
		//설정에서 경로설정을 해줬기 때문에 화이트리스트를 체크할 필요가 없음
		response.sendRedirect("/login?redirectURL="+requestURI);
		return false;
	}
	return true;
}

다음과 같이 경로를 등록해주면 된다.

@Configuration
public class WebConfig implements WebMvcConfigurer{
	@Override
	public void addInterceptors(InterceptorRegistry registry){
		registry.addInterceptor(new LoginCheckInterceptor())
						.order(2)
						.addPathPatterns("/**")//서블릿과 다른 패턴을 사용하므로 주의, 하위 패턴들을 뜻함
						.excludePatterns("/", "/members/add","/login", "/logout",
					"/css/**", "/*.ico", "/error"); //화이트 리스트와 같이 제외할 수 있음
	}
}

인터셉터를 사용할 때는 앞서 등록했던 필터의 @Bean을 해제하면 된다.

ArguemntResolver

HandlerMethodArgumentResolver를 상속받은 리졸버 클래스를 정의한다. Argument 리졸버는 요청에서 원하는 객체를 추출하기 위해 사용한다. 디스패처 서블릿이 호출되고 나면


서블릿->인터셉터->(Resolver)->HandlerAdapter-> 컨트롤러

어댑터를 찾는 과정에서 파라미터를 처리하기 위해 리졸버가 호출된다.

이 리졸버 클래스는 WebConfig 클래스에서 addArgumentResolver로 등록하면 되며, 2가지 메소드를 구현하면 된다.

파라미터가 조건을 만족하는지 검사하는 supportsParameter와 인자값을 리턴하는 resolveArgument 메소드이다.

@Override
public boolean supportsParmeter(MethodParameter parameter){
	public boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class);
	public boolean hasMemberType = Member.class.isAssignableFrom(parameter.getParameterType());
	return hasLoginAnnotation && hasMemberType;
}

@Override
public Object resolveArgument(Method Parameter parameter, 
ModelAndViewContainer modelAndViewContainer, 
NativeWebRequest nativeWebRequest, 
WebDataBinderFactory webDataBinderFactory)
throws Exception{
	HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
	HttpSession session = request.getSession(false);
	if(session==null){
		return null;
	}
	return session.getAttribute(SessionConst.LOGIN_MEMBER);
}

파라미터의 타입이나 어노테이션을 검사할 수도 있다. supportParameter가 true를 리턴해야 resolveArgument가 실행된다. 물론 어노테이션을 사용하기 위해서는 어노테이션 인터페이스를 정의해야 한다.

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME) //리플렉션을 사용하기 위해서 런타임으로 읽어야 한다.
public @interface Login{
}

컨트롤러에서는 다음과 같이 사용된다.


@GetMapping("/")
public String homeLoginResolver(@Login Member member, Model model){
	if(loginMember==null) //ArgumentResolver가 session에서 Member 객체를 리턴했다.
	

ArgumentResolver는 캐싱도 해주므로 supportsArgument 실행도 캐싱되어 어느정도 성능도 보장한다.

주의할 점

WebConfigure에 등록 시에 지금까지 필터나 인터셉터에 대한 의존성 주입을 하지는 않았지만, @Component로 스프링 빈으로 등록해서@Autowired 등으로 주입받을 수 있다.

아래의 흐름을 꼭 기억하자.

디스패처 서블릿은 핸들러 어댑터를 호출하고(이 때 파라미터에 따라 ArgumentResolver를 호출한다), 핸들러 어댑터는 핸들러(컨트롤러)를 호출한다. 컨트롤러가 반환한 ModelAndView를 가지고 postHandle이 호출된다.

profile
inudevlog.com으로 이전해용

0개의 댓글