1. intercepter란 뭘까?
인터셉터는 Spring MVC의 핵심 기능 중 하나로, 웹 애플리케이션에서 공통적인 처리를 재사용할 수 있게 해주는 강력한 도구입니다.
인터셉터(Interceptor)는 들어오는 요청과 나가는 응답을 가로채어 특정 로직을 수행할 수 있게 해주는 매커니즘을 제공합니다. 이는 AOP(Aspect-Oriented Programming)의 일종으로 볼 수 있으며, 컨트롤러(Controller)로 요청이 도달하기 전, 후 또는 완료된 후에 추가적인 처리를 하기 위해 사용됩니다.
인터셉터는 Spring MVC의 핵심 기능 중 하나로, 웹 애플리케이션에서 공통적인 처리를 재사용할 수 있게 해주는 강력한 도구입니다.
인터셉터(Interceptor)는 들어오는 요청과 나가는 응답을 가로채어 특정 로직을 수행할 수 있게 해주는 매커니즘을 제공합니다. 이는 AOP(Aspect-Oriented Programming)의 일종으로 볼 수 있으며, 컨트롤러(Controller)로 요청이 도달하기 전, 후 또는 완료된 후에 추가적인 처리를 하기 위해 사용됩니다.
대표적인 활용 사례
- 인증 및 권한 부여: 사용자의 인증 정보를 검사하여 요청이 유효한 사용자로부터 온 것인지 확인하고, 특정 자원에 대한 접근 권한을 확인합니다.
- 로깅 및 감사: 요청의 처리 과정에 대한 로깅을 수행하거나 감사 로그를 생성하여 시스템의 보안과 무결성을 유지하는 데 도움을 줍니다.
- 성능 모니터링: 요청 처리 시간을 측정하고 성능 문제를 식별하기 위한 메트릭을 수집합니다.
- 공통적인 응답 데이터 추가: 모든 응답에 공통적으로 포함되어야 하는 헤더나 데이터를 추가합니다.
인터셉터 구현 방법
먼저 딱 2가지만 기억해 봅시다.
- 동작 시키고자 하는 인터셉터 기능을 클래스로 만들어 준다. 단, 만들고 자 하는 해당 클래스에 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;
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 {
// 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;
@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.beans.factory.annotation.Autowired;
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;
import jakarta.websocket.server.PathParam;
import lombok.RequiredArgsConstructor;
@Controller // IoC 대상(싱글톤으로 관리)
@RequestMapping("/account")
@RequiredArgsConstructor
public class AccountController {
// 계좌 생성 화면 요청 - DI 처리
@Autowired
private final HttpSession session;
private final 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) {
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);
}
accountService.createAccount(dto, principal.getId());
return "redirect:/account/list";
}
/**
* 계좌 목록 화면 요청
* 주소설계 : http://localhost:8080/account/list, ..../
* @return list.jsp
*/
@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.getWAccountNumber() == 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 deposit.jsp
*/
@GetMapping("/deposit")
public String depositPage() {
return "account/deposit";
}
// 입금 처리 기능 만들기
@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";
}
/**
* 계좌 이체 화면 요청
* @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
*/
@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) {
// 유효성 검사
List<String> validTypes = Arrays.asList("all", "deposit", "withdrawal");
if(!validTypes.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);
return "account/detail";
}
}
UserController 생성자 수정 및 코드 추가
package com.tenco.bank.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.tenco.bank.dto.SignInDTO;
import com.tenco.bank.dto.SignUpDTO;
import com.tenco.bank.handler.exception.DataDeliveryException;
import com.tenco.bank.repository.model.User;
import com.tenco.bank.service.UserService;
import com.tenco.bank.utils.Define;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
@Controller
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
@Autowired
private final UserService userService;
private final HttpSession session;
/**
* 회원 가입 페이지 요청
* 주소 설계 : http://localhost:8080/user/sign-up
* @return signUp.jsp
*/
@GetMapping("/sign-up")
public String signUpPage() {
return "user/signUp";
}
/**
* 회원 가입 로직 처리 요청
* 주소 설계 : http://localhost:8080/user/sign-up
* @param dto
* @return
*/
@PostMapping("/sign-up")
public String signUpProc(SignUpDTO dto) {
if(dto.getUsername() == null || dto.getUsername().isEmpty()) {
throw new DataDeliveryException(Define.ENTER_YOUR_USERNAME, HttpStatus.BAD_REQUEST);
}
if(dto.getPassword() == null || dto.getPassword().isEmpty()) {
throw new DataDeliveryException(Define.ENTER_YOUR_PASSWORD, HttpStatus.BAD_REQUEST);
}
if(dto.getFullname() == null || dto.getFullname().isEmpty()) {
throw new DataDeliveryException(Define.ENTER_YOUR_FULLNAME, HttpStatus.BAD_REQUEST);
}
userService.createUser(dto);
return "redirect:/user/sign-in";
}
/**
* 로그인 화면 요청
* @return
*/
@GetMapping("/sign-in")
public String singInPage() {
return "user/signIn";
}
/**
* 로그인 요청 처리
* 주소설계 : http://localhost:8080/user/sign-in
* @return
*/
@PostMapping("/sign-in")
public String signProc(SignInDTO dto) {
if(dto.getUsername() == null || dto.getUsername().isEmpty()) {
throw new DataDeliveryException(Define.ENTER_YOUR_USERNAME, HttpStatus.BAD_REQUEST);
}
if(dto.getPassword() == null || dto.getPassword().isEmpty()) {
throw new DataDeliveryException(Define.ENTER_YOUR_PASSWORD, HttpStatus.BAD_REQUEST);
}
User principal = userService.readUser(dto);
session.setAttribute(Define.PRINCIPAL, principal);
return "redirect:/account/list";
}
/**
* 로그아웃 처리
* @return
*/
@GetMapping("/logout")
public String logout() {
session.invalidate();
return "redirect:/user/sign-in";
}
}
'Spring boot > Bank App 만들기' 카테고리의 다른 글
28.파일 업로드-1 단계(멀티파트가 뭘까?) (1) | 2024.09.25 |
---|---|
27.사용자 비밀번호 암호화 처리 (0) | 2024.09.25 |
25. 계좌 상세보기 페이징 처리 (0) | 2024.09.25 |
24.간단한 유틸 클래스 만들어 보기 (0) | 2024.09.25 |
23. 계좌 상세보기 - 2단계(기능,동적쿼리 구현) (1) | 2024.09.25 |