햄발
Bank App 만들기 - intercepter 활용(인증검사 공통 처리) 본문
학습 목표
1. intercepter란 뭘까?
2. 인터셉터 구현 클래스 만드는 방법과 인터셉트를 등록 처리
3. AccountController 인증 검사 제거 및 테스트
1. intercepter란 뭘까?
인터셉터는 Spring MVC의 핵심 기능 중 하나로, 웹 애플리케이션에서 공통적인 처리를 재사용할 수 있게 해주는 도구
인터셉터(Interceptor)는 들어오는 요청과 나가는 응답을 가로채어 특정 로직을 수행할 수 있게 해주는 매커니즘을 제공합니다. 이는 AOP(Aspect-Oriented Programming)의 일종으로 볼 수 있으며, 컨트롤러(Controller)로 요청이 도달하기 전, 후 또는 완료된 후에 추가적인 처리를 하기 위해 사용됩니다.
대표적인 활용 사례
- 인증 및 권한 부여
사용자의 인증 정보를 검사하여 요청이 유효한 사용자로부터 온 것인지 확인하고,
특정 자원에 대한 접근 권한을 확인합니다. - 로깅 및 감사
요청의 처리 과정에 대한 로깅을 수행하거나 감사 로그를 생성하여 시스템의 보안과 무결성을 유지하는 데 도와줌 - 성능 모니터링
요청 처리 시간을 측정하고 성능 문제를 식별하기 위한 메트릭을 수집합니다. - 공통적인 응답 데이터 추가
모든 응답에 공통적으로 포함되어야 하는 헤더나 데이터를 추가합니다.
인터셉터 구현 방법
- 동작 시키고자 하는 인터셉터 기능을 클래스로 만들어 준다.
단, 만들고 자 하는 해당 클래스에 HandlerInterceptor 인터페이스를 구현하거나 andlerInterceptorAdapter 클래스를 상속받아야 합니다. - 내가 만든 인터셉터를 Spring Boot 애플리케이션에 등록을 해주어야 동작 한다.
등록시에는 WebMvcConfigurer 인터페이스를 구현하는 설정 클래스에서 addInterceptors 메서드를 오버라이드하여 인터셉터를 등록합니다.
당연히 필요하다면 인터셉터를 구현한 사용자 정의 클래스를 여러개 정의해서 프로젝트에 활용 할 수 있습니다.
2. 인터셉터 구현 클래스 만드는 방법과 인터셉트를 등록 처리
handler / AuthInterceptor.java
- 컨트롤러 호출 전 : preHandle
- 컨트롤러 호출 후 : postHandle
- 요청 완료 이후 : afterCompletion, 뷰가 렌더링 된 이후에 호출된다.
AuthInterceptor.java
더보기
닫기
AuthInterceptor
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 // 1개의 class 단위로 등록할 때 사용 // IOC의 대상 (싱글톤 패턴)
public class AuthInterceptor implements HandlerInterceptor {
// preHandle 동작 흐름 (단 / 스프링부트 설정파일, 성정 클래스에 등록되어야 한다. : 특정 URL)
// 컨트롤러 들어 오기 전에 동작 하는 녀석
// boolean 의 값은 true/ false 이다.
// 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);
// 만약에 principal 이 null 이 아닌지 비교
if(principal == null) {
throw new UnAuthorizedException("로그인 먼저 해주세요", HttpStatus.UNAUTHORIZED);
}
return true;
} // end of preHandle()
// 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);
} // end of postHandle()
// 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);
} // end of afterCompletion()
}
config/WebMvcConfig.java 파일 생성 - 인터셉터 등록 하기
더보기
닫기
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 // 하나 이상의 bean을 등록
@RequiredArgsConstructor // 생성자 대신에 사용할 수 있음
public class WebMvcConfig implements WebMvcConfigurer {
// implements WebMvcConfigurer : 설정 파일로 사용할 수 있다.
@Autowired // DI
private final AuthInterceptor authInterceptor; // final 사용하면 @Autowired 사용못함
// @RequiredArgsConstructor // 생성자 대신에 사용할 수 있음
// 우리가 만들어 놓은 AuthInterceptor를 등록해야 함.
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/account/**")
.addPathPatterns("/auth/**");
} // end of addInterceptors()
}
AccountController 인증 검사 제거 및 테스트
더보기
닫기
AccountController .java
@SessionAttribute(Define.PRINCIPAL) User principal
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;
import jakarta.websocket.server.PathParam;
@Controller // IoC 대상(싱글톤으로 관리)
@RequestMapping("/account")
public class AccountController {
// 계좌 생성 화면 요청 - DI 처리
private final HttpSession session;
private final AccountService accountService;
// @Autowired
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) {
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";
}
}
'Spring boot' 카테고리의 다른 글
Bank App 만들기 - 파일 업로드(멀티파트) (0) | 2024.09.28 |
---|---|
Bank App 만들기 - 사용자 비밀번호 암호화 처리 (0) | 2024.09.28 |
Bank App 만들기 - 계좌 상세보기 페이징처리 (0) | 2024.09.27 |
Bank App 만들기 - 간단한 유틸클래스 만들기 (0) | 2024.09.27 |
Bank App 만들기 - 계좌 상세보기(기능, 동적쿼리 구현) (0) | 2024.09.27 |