[Spring boot] 인터셉터(Interceptor)

예림·2024년 6월 5일
7

Spring boot

목록 보기
5/8

인터셉터(Interceptor)

: 컨트롤러로 들어오는 HTTPRequest와 컨트롤러가 응답하는 HTTPResponse를 가로채는 역할을 한다.

특정 권한이 필요한 페이지 요청 시 권한이 있는지를 확인하거나, 세션이 만료되었는지 확인하는 등의 용도로 사용한다.

여러 페이지에서 로그인이 되었는지 확인해야 하는 것처럼 다수의 컨트롤러에 대해 동일한 기능을 적용해야 할 때 사용한다.


AOP와 인터셉터의 차이

  1. 실행 위치

    • Filter, 인터셉터는 서블릿 단위로 실행되지만, AOP는 메소드 앞에 Proxy패턴의 형태로 실행된다.
    • Filter는 DispatcherServlet 전, 인터셉터는 DispathcerServlet 후에 실행된다.
    • Filer → 인터셉터 → AOP 순서
  2. 역할

    • Filter

      • 요청과 응답을 거른 뒤 정제하는 역할
      • 인코딩 변환 처리, XSS방어 등의 요청/응답에 대한 변경/확인 작업이 추가된다.
      • 스프링 외부에 존재하여 스프링과 무관한 자원에 대해 동작한다.
      • 주소로 대상을 구분한다.
      • web.xml에 설정한다.
    • 인터셉터

      • 요청에 대한 작업 전/후로 가로챈다.
      • Controller(Handler)에 관한 요청과 응답에 대해 처리
      • 스프링 내부에 존재하여 스프링의 모든 빈 객체에 접근 가능하다.
      • 로그인 체크, 권한 체크, 프로그램 실행시간 계산작업 로그확인 등의 작업을 처리한다.
      • 주소로 대상을 구분한다.
    • AOP

      • 메소드 전후의 지점에 자유롭게 설정한다.
      • 로깅, 트랜잭션, 에러처리 등 비즈니스단의 메소드에서 조금 더 세밀하게 조정하고 싶을 때 사용한다.
      • 주소, 파라미터, 어노테이션 등 다양한 방법으로 대상을 지정한다.

인터셉터 구현

스프링에서는 세 가지 시점에 인터셉터 처리가 가능하다.

  • 컨트롤러 실행 전

    • boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception
  • 컨트롤러 실행 후, 아직 뷰를 실행하기 전

    • boolean postHandle(HttpServletRequest req, HttpServletResponse res, Object handler, ModelAndView modelAndView) throws Exception
  • 뷰를 실행한 이후

    • void AfterCompletion(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex) throws Exception

인터셉터 구현 순서

  1. HandlerInterceptor를 구현한 클래스를 정의한다.

    → @Component : preHandle(), postHandle(), afterCompletion()

  2. WebMvcConfigurer 인터페이스를 구현한 클래스를 정의하고 1번에서 만든 클래스를 등록해줘야 한다.

    → @Configuration : addInterceptors()


인터셉터 구현 예제

UserController.java → 컨트롤러

@GetMapping("/login")
public String loginForm(Model model) {

        model.addAttribute("loginForm", new LoginForm());
        return "login";
}

@PostMapping("/login")
public String login(@ModelAttribute("loginForm") LoginForm form, BindingResult bindingResult, HttpServletResponse response) {

        User loginUser = service.login(form.getLoginId(), form.getPassword());
        log.info("login? {}", loginUser);

        if (loginUser == null) {
            bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다.");
        }

        if (bindingResult.hasErrors()) {
            return "login";
        }

        Cookie cookie = new Cookie("idx", String.valueOf(loginUser.getIdx()));
        cookie.setMaxAge(60 * 60);  // 쿠키 유효 시간 : 1시간
        response.addCookie(cookie);

        // 로그인 성공 처리
        return "redirect:/main";
}

@GetMapping("/user/main")
public String userList(Model model) {
        List<User> users = service.getAllUsers();
        model.addAttribute("users", users);
        return "user/main";
}

@GetMapping("/user/info/{userId}")
public String userInfo(@PathVariable String userId, Model model) {
        log.info("Fetching user info for userId: {}", userId);
        User user = service.getLoginUser(userId);
        if (user == null) {
            log.warn("User not found for userId: {}", userId);
            return "redirect:/login";
        }
        log.info("User found: {}", user);
        model.addAttribute("user", user);
        return "user/info";
}

로그인을 한 사용자만 글을 열람할 수 있도록 권한이 없는 사용자 접근 시 Interceptor가 요청을 빼앗아 /login페이지로 리다이렉트한다.

AuthInterceptor.java → 인터셉터 구현(Session)

package org.study.board.config;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response,Object handler) throws Exception {
        HttpSession session = request.getSession();
        // 사용자가 로그인을 하면 세션 메모리 영역에 user 키-값 구조로 저장 처리할 예정
        Object user=session.getAttribute("user");
        System.out.println(user);

        System.out.println("preHandler 동작 확인");
        if(user==null){
            response.sendRedirect("/login");
            return false;
        }
        return true;
    }
}

preHandle() 메서드는 false 리턴 시 다음 컨트롤러를 실행하지 않는다.

postHandle() 메서드는 컨트롤러 익셉션 발생 시 실행하지 않는다.

afterCompletion() 메서드는 뷰가 클라이언트에 응답을 전송한 뒤에 실행된다. 컨트롤러에서 익셉션 발생 시 ex 파라미터에 전달되고, 발생하지 않으면 null이 전달된다. 컨트롤러 실행 후 로그를 남기거나, 실행 시 간을 기록하는 등의 후처리를 하기 위한 메서드이다.

AuthInterceptor.java → 인터셉터 구현(Cookie)

package org.study.board.config;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Arrays;
import java.util.Optional;

@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response,Object handler) throws Exception {

        // Cookie 방식
        // 쿠키에서 사용자 토큰을 찾습니다.
        Optional<Cookie> userTokenCookie = Arrays.stream(request.getCookies())
                .filter(cookie -> cookie.getName().equals("idx"))
                .findFirst();

        // 사용자 토큰이 존재하지 않으면 로그인 페이지로 리다이렉트합니다.
        if (!userTokenCookie.isPresent()) {
            response.sendRedirect("/login");
            return false;
        }

        // 토큰이 존재하면 인증을 성공한 것으로 간주합니다.
        return true;
    }
}

WebMvcConfig.java → 인터셉터 적용

package org.study.board.config;

import org.springframework.beans.factory.annotation.Autowired;
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 WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private AuthInterceptor sessionInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 명시하는 요청 설계 주소에서만 동작하도록

        // localhost:8080/hello <-- 동작x
        // localhost:8080/user/info/{userId} <-- 인터셉터 동작
        registry.addInterceptor(sessionInterceptor)
                .addPathPatterns("/board/**"); // board 아래에 있는것만 가로챔
    }
}
  1. 인터셉터를 빈으로 등록
  2. WebMvcConfigurer의 addInterceptors() 메서드로 인터셉터 설정
    2-1. InterceptorRegister 객체의 addInterceptor() 메서드로 등록한 인터셉터를 설정
    2-2. InterceptorRegister 객체의 addPathPatterns("ant 패턴")으로 인터셉트할 url 설정

참고자료
https://spyair.tistory.com/161
https://velog.io/@fortice/Spring-세션-인터셉터-쿠키

profile
백엔드 개발하는 사람

0개의 댓글