[Spring Boot] 50. 스프링부트 블로그 v2 (JPA) (20) 인증 자동화 - Interceptor

김주희's avatar
Apr 10, 2025
[Spring Boot] 50. 스프링부트 블로그 v2 (JPA) (20) 인증 자동화 - Interceptor
notion image

1. 인증 체크와 Inteceptor

Exception을 처리할 때 내가 DispatcherServlet을 직접적으로 만지지는 못하지만 DS 영역에서 처리할 수 있도록 하기 위해 GlobalExceptionJHandler를 만들어서 사용했다. 즉, @RestControllerAdvice와 @ExceptionHandler를 사용하여 DS에서 try-catch 중 try에서는 invoke(DS이 요청을 처리할 Controller의 메서드를 호출하는 과정)하고, invoke 과정에서 생기는 예외를 처리할 catch에 접근할 수 있었다. (e.g. catch에서 return Resp.fail(401,e.getMessage()) 실행됨)

1. 인증 체크는 어디서? 어떻게?

인증 체크는 보통 주소로 필터링 해서 처리하게 된다. uri를 기준으로 /board/{id}를 주소로 갖는 메서드 내부에서는 인증이 필요하면 /board/{id}를 필터링해서 인증 없이 해당 주소로 들어올 경우 걸러낼 수 있다.
이때 주소를 필터링할 filter는 Tomcat과 DS 사이에 위치하게 되는데 Tomcat으로부터 전달받은 req와 resp 중 req에 접근해서 주소를 확인하고 session에 있는지 등을 확인하게 된다.
그러나 filter를 사용해서 인증 체크를 하게 될 경우 DS보다 앞에 위치하게 되므로 여태까지 사용한 예외처리 방식인 throw new Exception401 등을 사용하지 못한다. 직접 PrintWriter 버퍼를 만들어서 응답해야 하는 번거로움이 있기 때문에 DS 앞단에 위치하는 filter를 사용하지 않는다.
⇒ 따라서 Spring의 도움을 받아서 DS에서 Exception을 처리하기 위해 Interceptor를 사용한다.
 

2. Interceptor

Interceptor는 DS이 Controller의 메서드를 호출하는 invoke 과정의 전후를 preHandle과 postHandle 메서드를 통해 관리할 수 있다.
  1. preHandle
    1. invoke 실행 전 즉, DispatcherServlet이 Controller로 요청을 전달하기 전에 호출된다.
    2. request.getSession을 통해 session에 내가 찾고자 하는 값이 있으면 true를 반환해 invoke 되도록 하고 값이 없으면 throw를 통해 exception을 터트린
  1. postHandle
    1. DispatcherServlet이 Controller의 메서드 실행 후 ~ view 렌더링 직전 사이에 호출된다.
  1. aftercomplete
    1. FileReader로 view 이름이 있는 위치에 가서 view를 읽는다.
    2. view가 렌더링된 후 즉, html(view)이 만들어 진 후에 실행된다.
 

+) interface와 default - implements HandlerInterceptor

interface를 implements하면 interface의 메서드를 무조건 구현해야 했다. 그러나 interface의 내부 메서드를 default 메서드로 만들면 무조건적으로 구현할 필요 없이 원하는 것만 구현 가능하다.
HandlerInterceptor을 implement
 
notion image
notion image
 
 
ExceptionHandler가 DS와 같은 계층에 위치 interface 내부 메서드 앞에 default를 붙이면 구현 안해도 됨 - 몸체({})를 만들 수 있음? Adapter - 안쓰는 메서드 걸러낼 수 있음 (디자인 패턴 공부하면 더 잘 알 수 있는 adapter 잠온당... 골라서 써 = dafault O / 강제성 = dafault X default 안붙어있으면 딱 하나만 lamda statement로 쓸 수 있음
 
 
 
 
 
 
 
 
addPathPatterns(”/s/**”) : 인증이 필요한 것들은 모두 주소 앞에 /s/를 붙여서 해결 가능
ajax로 인한 json 쪽은 주소에 /api/ 붙여서
 
 
 
 
 

2. 인증 전용 interceptor

 
notion image
notion image
package shop.mtcoding.blog._core.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import shop.mtcoding.blog._core.interceptor.LoginInterceptor; // WebMvcConfigurer : 설정하고 싶은 것들이 들어있는 @Configuration // WebMvcConfigurer를 implements해서 WebMvcConfig를 IoC에 등록해야됨 (@Component 포함되어있음) public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/user/**") // 언제 interceptor가 발동하게 할 건지 .addPathPatterns("/board/**") .addPathPatterns("/love/**") // 지워도 되지만 그냥 두고 하나 더 등록하는 게 낫다 .addPathPatterns("/api/love/**") .addPathPatterns("/reply/**") .excludePathPatterns("/board/{id:\\d+}"); // 예외자리 - {id}는 정규표현식으로 처리 } }
package shop.mtcoding.blog._core.interceptor; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.springframework.web.servlet.HandlerInterceptor; import shop.mtcoding.blog._core.error.ex.Exception401; import shop.mtcoding.blog._core.error.ex.ExceptionApi401; import shop.mtcoding.blog.user.User; public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String uri = request.getRequestURI(); // request가 담고 있는 것들 예전에 찍어봣는데 기억안나면 해보기ㅣㅣ HttpSession session = request.getSession(); // request로 session 영역에 접근할 수 있는 // 인증이 안되면 throw가 터짐 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) { if (uri.contains("/api")) { throw new ExceptionApi401("인증이 필요합니다."); // 만약에 직접 처리해야한다면 (filter로 해야하면) // response.setStatus(401); // response.setHeader("Content-Type", "application/json"); // PrintWriter out = response.getWriter(); // Resp<?> resp = Resp.fail(401, "인증이 필요합니다"); // ObjectMapper mapper = new ObjectMapper(); // String responseBody = mapper.writeValueAsString(resp); // out.println(responseBody); // return false; } else { throw new Exception401("인증이 필요합니다."); // 만약에 직접 처리해야한다면 // response.setStatus(401); // response.setHeader("Content-Type", "text/html"); // PrintWriter out = response.getWriter(); // out.println(Script.href("인증이 필요합니다", "/login-form")); // return false; } } return true; // true : controller 호출O / false : controller 안때림 } }
Share article

jay0628