Springboot

[Springboot] 30. Intercepter 활용 (인증검사 공통 처리)

Song hyun 2024. 8. 13. 09:48
728x90
반응형

[Springboot] 30. Intercepter 활용 (인증검사 공통 처리)

 

1. Intercepter란?

(1) Intercepter의 개념:

-인터셉터는 Spring MVC의 핵심 기능 중 하나로, 웹 애플리케이션에서 공통적인 처리를 재사용할 수 있게 해주는 강력한 도구이다.

 

(2) Intercepter의 활용 사례:

 

(3) Intercepter의 작동 원리

 

(4) filter와 intercepter

 

 

2. Intercepter 사용해보기

(1) Intercepter 클래스 생성하기

package com.tenco.bank.handler;

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

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@Component // IoC 대상(싱글톤 패턴)
public class AuthIntercepter implements HandlerInterceptor{
	
	// prehandle 동작 흐름 (단, 스프링부트 설정 파일/클래스에 등록되어야 한다.)
	// 컨트롤러 들어오기 전에 동작하는 녀석!
	// true --> 컨트롤러 안으로 들여 보낸다.
	// false --> 컨트롤러 안으로 못들어감
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		return HandlerInterceptor.super.preHandle(request, response, handler);
	}
	
	
	// 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);
	}
	
}

 

 

(2) Controller 전 인증검사 메서드 만들기

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 AuthIntercepter implements HandlerInterceptor{
	
	// prehandle 동작 흐름 (단, 스프링부트 설정 파일/클래스에 등록되어야 한다.)
	// 컨트롤러 들어오기 전에 동작하는 녀석!
	// 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);
	}
	
}

 

 

(3) WebMvcConfigurer을 구현해 인터셉터 등록하기

package com.tenco.bank.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.tenco.bank.handler.AuthIntercepter;

import lombok.RequiredArgsConstructor;

@Configuration // <-- 하나의 클래스를 IOC 하고 싶다면 사용
@RequiredArgsConstructor 
public class WebMvcConfig implements WebMvcConfigurer {
	
	@Autowired // DI
	private final AuthIntercepter authIntercepter;
	
	// @RequiredArgsConstructor <-- 생성자 대신 사용 가능!
	
	// 우리가 만든 AuthIntercepter를 등록해야 한다.
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(authIntercepter)
		.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 org.springframework.web.servlet.ModelAndView;

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.interfaces.AccountRepository;
import com.tenco.bank.repository.interfaces.HistoryRepository;
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 lombok.RequiredArgsConstructor;

@Controller // IoC 대상(싱글톤으로 관리)
@RequestMapping("/account")
@RequiredArgsConstructor
public class AccountController {
	
	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
	 * @param dto
	 * @return
	 */
	@PostMapping("/save")
	public String saveProc(SaveDTO dto, @SessionAttribute(Define.PRINCIPAL) User principal) {
		// 1. form 데이터 추출(파싱 전략)
		// 2. 인증 검사
		// 3. 유효성 검사
		// 4. 서비스 호출
		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,
			@RequestParam(name="currentPage", defaultValue="1") Integer currentPage, 
			@RequestParam(name="totalPage", defaultValue="3") Integer totalPage, 
			@SessionAttribute(Define.PRINCIPAL) User principal) {

		
		if(currentPage==null) {
			currentPage=1;
		}
		Integer limit=2; // 한 페이지당 내역 수
		Integer offset=currentPage; // 몇번부터 뽑을지
		// 총 내역 수
		Integer totalAccount=(accountService.readAccountListByUserId(principal.getId())).size();
		// 총 페이지 수
		Integer totalPages=(int)Math.ceil((double)totalAccount/limit);
		
		// 페이지별 내역 수
		List<Account> accountList=accountService.readAccountListByUserIdForPaging(principal.getId(),limit,currentPage);
		if(accountList.isEmpty()) {
			model.addAttribute("accountList",null);
		} else {
			model.addAttribute("accountList",accountList);
		}
		
		
		model.addAttribute("currentPage", currentPage);
		model.addAttribute("totalPages", totalPages);
		model.addAttribute("totalPages", totalPages);
		// JSP 데이터를 넣어주는 방법
		return "account/list";
	}
	
	
	/**
	 * 출금 페이지 요청
	 * @return
	 */
	@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) {
			throw new DataDeliveryException(Define.ENTER_YOUR_ACCOUNT_NUMBER, HttpStatus.BAD_REQUEST);
		}
		if(dto.getWAccountPassword()==null) {
			throw new DataDeliveryException(Define.ENTER_YOUR_PASSWORD, HttpStatus.BAD_REQUEST);
		}
		
		accountService.updateAccountWithdrawal(dto, principal.getId());
		
		return "redirect:/account/list";
	}
	
	/**
	 * 입금 페이지 요청
	 */
	@GetMapping("/deposit")
	public String depositPage() {
				
		return "account/deposit";
	}
	
	/**
	 * 
	 */
	
	/**
	 * 입금 요청 처리
	 */
	@PostMapping("/deposit")
	public String depositProc(DepositDTO dto, @SessionAttribute(Define.PRINCIPAL) User principal) {
		
		// 입금시 검사해야 할 것
		// 1. 계좌가 존재하는지
		// 2. 돈이 존재하는지
		
		if (dto.getAmount() == null) {
            throw new DataDeliveryException(Define.ENTER_YOUR_BALANCE, HttpStatus.BAD_REQUEST);
        }
		if(dto.getAmount()<=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);
		}
		
		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) {
		
		// 입금시 검사해야 할 것
		// 1. 계좌가 존재하는지
		// 2. 돈이 존재하는지
		System.out.println(dto);
		
		// 입금-출금 계좌 중복 여부 확인 확인
		if(dto.getDAccountNumber().equals(dto.getWAccountNumber())) {
			throw new DataDeliveryException(Define.INVALID_INPUT, HttpStatus.BAD_REQUEST);
		}
		// 입금 금액 널 확인
		if (dto.getAmount() == null) {
            throw new DataDeliveryException(Define.ENTER_YOUR_BALANCE, HttpStatus.BAD_REQUEST);
        }
		// 입금 금액 - 확인
		if(dto.getAmount()<=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.getWAccountNumber()==null) {
			throw new DataDeliveryException(Define.ENTER_YOUR_ACCOUNT_NUMBER, HttpStatus.BAD_REQUEST);
		}
		// 출금 계좌 비밀번호 널 확인
		if(dto.getPassword()==null) {
			throw new DataDeliveryException(Define.ENTER_YOUR_PASSWORD, HttpStatus.BAD_REQUEST);
		}
		
		accountService.updateAccountTransfer(dto, principal.getId());
		
		return "redirect:/account/list";
	}
	
	/**
	 * 계좌 상세 보기 페이지
	 * 주소 설계 : http://localhost:800/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) {

		// 유효성 검사
		// arrays.asList() -> array 선언과 동시에 초기화해주는 메서드
		// 사용자가 선택한 타입(입출금/입금/출금 검색)을 검사한다.
		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";
	}
	
	
}
728x90
반응형