intercepter란 뭘까?
인터셉터는 Spring MVC의 핵심 기능 중 하나로, 웹 애플리케이션에서 공통적인 처리를 재사용할 수 있게 해주는 강력한 도구이다.
인터셉터(Interceptor)는 들어오는 요청과 나가는 응답을 가로채어 특정 로직을 수행할 수 있게 해주는 매커니즘을 제공한다. 이는 AOP(Aspect-Oriented Programming)의 일종으로 볼 수 있으며, 컨트롤러(Controller)로 요청이 도달하기 전, 후 또는 완료된 후에 추가적인 처리를 하기 위해 사용된다.
대표적인 활용 사례
- 인증 및 권한 부여 : 사용자의 인증 정보를 검사하여 요청이 유효한 사용자로부터 온 것인지 확인하고, 특정 자원에 대한 접근 권한을 확인한다.
- 로깅 및 감사 : 요청의 처리 과정에 대한 로깅을 수행하거나 감사 로그를 생성하여 시스템의 보안과 무결성을 유지하는 데 도움을 준다.
- 성능 모니터링 : 요청 처리 시간을 측정하고 성능 문제를 식별하기 위한 메트릭을 수집한다.
- 공통적인 응답 데이터 추가 : 모든 응답에 공통적으로 포함되어야 하는 헤더나 데이터를 추가한다.
인터셉터 구현 방법
먼저 딱 2가지만 기억해 보자.
- 동작 시키고자 하는 인터셉터 기능을 클래스로 만들어 준다. 단, 만들고 자 하는 해당 클래스에 HandlerInterceptor 인터페이스를 구현하거나 HandlerInterceptorAdapter 클래스를 상속받아야 한다.
- 내가 만든 인터셉터를 Spring Boot 애플리케이션에 등록을 해주어야 동작 한다. 등록시에는 WebMvcConfigurer 인터페이스를 구현하는 설정 클래스에서 addInterceptors 메서드를 오버라이드하여 인터셉터를 등록한다.
당연히 필요하다면 인터셉터를 구현한 사용자 정의 클래스를 여러개 정의해서 프로젝트에 활용 할 수 있다.
인터셉터 구현 클래스 만드는 방법과 인터셉트를 등록 처리
package com.tenco.bank.handler;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.tenco.bank.handler.exception.UnAuthorizedException;
import com.tenco.bank.repository.model.User;
import com.tenco.bank.utils.Define;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
@Component // IoC 대상 (싱글톤 패턴)
public class AuthInterceptor implements HandlerInterceptor{
// preHandle 동작 흐름 (단, 스프링부트 설정 파일, 설정 클래스에 등록이 되어야 한다 : 특정 URL)
// 컨트롤러 들어 오기 전에 동작하는 녀석
// true -> 컨트롤러 안으로 들여 보낸다.
// false -> 컨트롤러 안으로 못 들어간다.
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
HttpSession session = request.getSession();
User principal = (User)session.getAttribute(Define.PRINCIPAL);
if(principal == null) {
throw new UnAuthorizedException("로그인 먼저 해주세요.", HttpStatus.UNAUTHORIZED);
}
return true;
}
// postHandle
// 뷰가 렌더링 되기 바로 전에 콜백 되는 메서드
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
// afterCompletion
// 요청 처리가 완료 된 후, 즉 뷰가 완전 렌더링이 된 후에 호출된다.
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
- 컨트롤러 호출 전 : preHandle
- 컨트롤러 호출 후 : postHandle
- 요청 완료 이후 : afterCompletion, 뷰가 렌더링 된 이후에 호출된다.
config / WebMvcConfig.java 파일 생성 - 인터셉터 등록 하기
package com.tenco.bank.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;
import com.tenco.bank.handler.AuthInterceptor;
import lombok.RequiredArgsConstructor;
@Configuration // 하나의 클래스를 IOC 하고 싶다면 사용
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired // DI
private final AuthInterceptor authInterceptor;
// @RequiredArgsConstructor <- 생성자 대신 사용 가능
// 우리가 만들어 놓은 AuthInterceptor 를 등록해야 한다.
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor).addPathPatterns("/account/**")
.addPathPatterns("/auth/**");
}
}
AccountController 인증 검사 제거 및 테스트
package com.tenco.bank.controller;
import java.util.Arrays;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttribute;
import com.tenco.bank.dto.DepositDTO;
import com.tenco.bank.dto.SaveDTO;
import com.tenco.bank.dto.TransferDTO;
import com.tenco.bank.dto.WithdrawalDTO;
import com.tenco.bank.handler.exception.DataDeliveryException;
import com.tenco.bank.handler.exception.UnAuthorizedException;
import com.tenco.bank.repository.model.Account;
import com.tenco.bank.repository.model.HistoryAccount;
import com.tenco.bank.repository.model.User;
import com.tenco.bank.service.AccountService;
import com.tenco.bank.utils.Define;
import jakarta.servlet.http.HttpSession;
@Controller // IoC 대상(싱글톤으로 관리)
@RequestMapping("/account")
public class AccountController {
// 계좌 생성 화면 요청 DI 처리
private final HttpSession session;
private final AccountService accountService;
public AccountController(HttpSession session, AccountService accountService) {
this.session = session;
this.accountService = accountService;
}
/**
* 계좌 생성 페이지 요청 주소 설계 : http://localhost:8080/account/save
*
* @return save.jsp
*/
@GetMapping("/save")
public String savePage() {
return "account/save";
}
/**
* 계좌 생성 기능 요청 주소 설계 : http://localhost:8080/account/save
*
* @return 추후 계좌 목록 페이지 이동 처리
*/
@PostMapping("/save")
public String saveProc(SaveDTO dto, @SessionAttribute(Define.PRINCIPAL) User principal) {
// 1. form 데이터 추출 (파싱전략) SaveDTO
// 3. 유효성 검사
if (dto.getNumber() == null || dto.getNumber().isEmpty()) {
throw new DataDeliveryException(Define.ENTER_YOUR_ACCOUNT_NUMBER, HttpStatus.BAD_REQUEST);
}
if (dto.getPassword() == null || dto.getPassword().isEmpty()) {
throw new DataDeliveryException(Define.ENTER_YOUR_PASSWORD, HttpStatus.BAD_REQUEST);
}
if (dto.getBalance() == null || dto.getBalance() <= 0) {
throw new DataDeliveryException(Define.ENTER_YOUR_BALANCE, HttpStatus.BAD_REQUEST);
}
// 4. 서비스 호출
accountService.createAccount(dto, principal.getId());
return "redirect:/account/list";
}
/**
* 계좌 목록 화면 요청 주소 설계 : http://localhost:8080/account/list , ../
*
* @return
*/
@GetMapping({ "/list", "/" })
public String listPage(Model model) {
// 1. 인증 검사
User principal = (User) session.getAttribute(Define.PRINCIPAL);
if (principal == null) {
throw new UnAuthorizedException(Define.NOT_AN_AUTHENTICATED_USER, HttpStatus.UNAUTHORIZED);
}
// 2. 유효성 검사
// 3. 서비스 호출
List<Account> accountList = accountService.readAccountListByUserId(principal.getId());
if (accountList.isEmpty()) {
model.addAttribute("accountList", null);
} else {
model.addAttribute("accountList", accountList);
}
// JSP 데이터를 넣어주는 방법
return "account/list";
}
/**
* 출금 페이지 요청
*
* @return withdrawal.jsp
*/
@GetMapping("/withdrawal")
public String withdrawalPage() {
return "account/withdrawal";
}
@PostMapping("/withdrawal")
public String withdrawalProc(WithdrawalDTO dto, @SessionAttribute(Define.PRINCIPAL) User principal) {
// 유효성 검사 (자바 코드를 개발) -> 스프링 부트 @Valid 라이브러리가 존재
if(dto.getAmount() == null) {
throw new DataDeliveryException(Define.ENTER_YOUR_BALANCE, HttpStatus.BAD_REQUEST);
}
if(dto.getAmount().longValue() <= 0) {
throw new DataDeliveryException(Define.W_BALANCE_VALUE, HttpStatus.BAD_REQUEST);
}
if(dto.getWAccountPassword() == null) {
throw new DataDeliveryException(Define.ENTER_YOUR_ACCOUNT_NUMBER, HttpStatus.BAD_REQUEST);
}
if(dto.getWAccountPassword() == null || dto.getWAccountPassword().isEmpty()) {
throw new DataDeliveryException(Define.ENTER_YOUR_PASSWORD, HttpStatus.BAD_REQUEST);
}
accountService.updateAccountWithdraw(dto, principal.getId());
return "redirect:/account/list";
}
/**
* 입금 페이지 요청
* @return
*/
@GetMapping("/deposit")
public String depositPage() {
return "account/deposit";
}
// 입금 처리 기능 만들기
@PostMapping("/deposit")
public String depositProc(DepositDTO dto, @SessionAttribute(Define.PRINCIPAL) User principal) {
// 2. 유효성 검사
if(dto.getAmount() == null) {
throw new DataDeliveryException(Define.D_BALANCE_VALUE, HttpStatus.BAD_REQUEST);
}
if(dto.getAmount().longValue() <= 0) {
throw new DataDeliveryException(Define.ENTER_YOUR_BALANCE, HttpStatus.BAD_REQUEST);
}
if(dto.getDAccountNumber() == null) {
throw new DataDeliveryException(Define.ENTER_YOUR_ACCOUNT_NUMBER, HttpStatus.BAD_REQUEST);
}
if(dto.getDAccountNumber() != dto.getDAccountNumber()) {
throw new DataDeliveryException(Define.NOT_ACCOUNT_OWNER, HttpStatus.BAD_REQUEST);
}
accountService.updateAccountDeposit(dto, principal.getId());
return "redirect:/account/list";
}
/**
* 계좌 이체 화면 요청
* @return transfer.jsp
*/
@GetMapping("/transfer")
public String transferPage() {
return "account/transfer";
}
/**
* 계좌 이체 기능 구현
* @param TransferDTO
* @return redirect:/account/list
*/
@PostMapping("/transfer")
public String transferProc(TransferDTO dto, @SessionAttribute(Define.PRINCIPAL) User principal) {
// 2. 유효성 검사
if (dto.getAmount() == null) {
throw new DataDeliveryException(Define.ENTER_YOUR_BALANCE, HttpStatus.BAD_REQUEST);
}
if (dto.getAmount().longValue() <= 0) {
throw new DataDeliveryException(Define.D_BALANCE_VALUE, HttpStatus.BAD_REQUEST);
}
if (dto.getWAccountNumber() == null || dto.getWAccountNumber().isEmpty()) {
throw new DataDeliveryException("출금하실 계좌번호를 입력해주세요.", HttpStatus.BAD_REQUEST);
}
if (dto.getDAccountNumber() == null || dto.getDAccountNumber().isEmpty()) {
throw new DataDeliveryException("이체하실 계좌번호를 입력해주세요.", HttpStatus.BAD_REQUEST);
}
if (dto.getPassword() == null || dto.getPassword().isEmpty()) {
throw new DataDeliveryException(Define.ENTER_YOUR_PASSWORD, HttpStatus.BAD_REQUEST);
}
// 서비스 호출
accountService.updateAccountTransfer(dto, principal.getId());
return "redirect:/account/list";
}
/**
* 계좌 상세 보기 페이지
* 주소 설계 : http://localhost:8080/account/detail/${1}?type=all, deposit, withdraw
* @return detail.jsp
*/
@GetMapping("/detail/{accountId}")
public String detail(@PathVariable (name = "accountId") Integer accountId,
@RequestParam (required = false, name = "type")String type,
@RequestParam (name = "page", defaultValue = "1") int page,
@RequestParam (name = "size", defaultValue = "2") int size,
Model model) {
// 2. 유효성 검사
List<String> vaildTypes = Arrays.asList("all", "deposit", "withdrawal");
if(!vaildTypes.contains(type)) {
throw new DataDeliveryException("유효하지 않은 접근입니다.", HttpStatus.BAD_REQUEST);
}
// 페이지 갯수를 계산하기 위해서 총 페이지 수를 계산해줘야한다.
int totalRecords = accountService.countHistoryByAccountIdAndType(type, accountId);
int totalPages = (int)Math.ceil((double)totalRecords / size);
Account account = accountService.readAccountById(accountId);
List<HistoryAccount> historyList = accountService.readHistoryByAccountId(type, accountId, page, size);
model.addAttribute("account", account);
model.addAttribute("historyList", historyList);
model.addAttribute("currentPage", page);
model.addAttribute("totalPages", totalPages);
model.addAttribute("type", type);
model.addAttribute("size", size);
System.out.println("11111111111111111");
return "account/detail";
}
}
728x90
'Spring boot > Bank App 만들기(deployment)' 카테고리의 다른 글
Bank App 만들기 ( deployment ) - 파일 업로드 - 1 단계 ( 멀티파트 ) (0) | 2024.08.13 |
---|---|
Bank App 만들기 ( deployment ) - 사용자 비밀번호 암호화 처리 (0) | 2024.08.13 |
Bank App 만들기 ( deployment ) - 계좌 상세보기 페이징 처리 (0) | 2024.08.12 |
Bank App 만들기 ( deployment ) - 간단한 유틸 클래스 만들어 보기 (1) | 2024.08.12 |
Bank App 만들기 ( deployment ) - 2단계 ( 기능, 동적쿼리 구현 ) (0) | 2024.08.09 |