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