본문 바로가기
프로젝트/개인 프로젝트 V2

7) 오류처리와 예외처리

by Thumper 2023. 6. 14.

검증 로직을 추가해야 한다.

컨트롤러의 중요한 역할중 하나는 HTTP 요청이 정상인지 검증하는 것이다.
웹 서비스는 폼 입력시 오류가 발생하면, 고객이 입력한 데이터를 유지한 상태로 어떤 오류가 발생했는지 친절하게 알려주어야 한다.


웹서비스 프로젝트에 검증 요구사항을 추가해보자.

상품 등록/수정 할 때,

  • 가격, 수량에 문자가 들어가면 안된다.
  • 이미지 1개 이상 필수 입력해야 한다.
  • 상품명은 필수 입력값이다.
  • 입력을 잘못한 경우, 다시 입력 폼으로 돌아오도록 한다.

로그인 요구사항

  • 로그인 사용자만 상품에 접근하고, 관리할 수 있다.
  • 로그인 하지 않은 사용자는 상품 관리에 접근하면 로그인 화면으로 이동된다.
  • 로그인 할 때, 비밀번호 잘못 입력한 경우 null을 반환한다.
  • 로그인에 실패한 경우, "아이디 또는 비밀번호가 맞지 않습니다." 라는 경고와 함께 로그인 폼으로 돌아오도록 한다.
  • 로그인에 성공하면, 상품 관리와 로그아웃 버튼을 확인할 수 있다.
  • 로그아웃했을 때, 로그인 폼으로 이동된다.

오류 페이지

  • 사용자가 잘못된 요청을 했거나 서버오류가 났을 때 보여줄 뷰를 추가한다.

 

 

 

스프링이 제공하는 검증 오류 처리 방법 BindingResult

  • bindingResult.hasErrors() 함수로 값이 알맞은지 체크할 수 있다.
  • BindingResult 파라미터의 위치는 @ModelAttribute Item item 다음에 와야 한다.
         if (bindingResult.hasErrors()) {
              log.info("errors={}", bindingResult);
              return "item/addItemForm";
          }
     

입력을 받지 못한 경우

로그인 오류 메시지

 



패스워드를 잘못 입력한 경우

패스워드 잘못 입력했을 때

 

 

이런 식으로 검증을 넣어, 상품 등록/수정 시 타입오류가 생겼을 때 다시 입력폼으로 돌아오도록 한다.

로그인 할 때, 아이디는 잘못 입력해도 유지된 채 반환되지만, 비밀번호는 잘못 입력한 경우 null로 반환받는다.

 

 

오류 메시지를 체계적으로 다루어보자.

errors 메시지 파일 생성 :errors.properties 별도의 파일로 관리해보자.
스프링 부트가 해당 메시지 파일을 인식할 수 있게 다음 설정을 추가한다.

이렇게하면 messages.properties , errors.properties 두 파일을 모두 인식한다. application.properties에 설정 추가한다.

spring.messages.basename=messages,errors

 

타임리프 스프링 검증 오류 통합 기능

타임리프는 스프링의 BindingResult 를 활용해서 편리하게 검증 오류를 표현하는 기능을 제공한다.

  • th:errors : 해당 필드에 오류가 있는 경우에 태그를 출력한다. "th:if"의 편의 버전이다.
  • th:errorclass : "th:field"에서 지정한 필드에 오류가 있으면 class 정보를 추가한다.

이런 식으로 상품 등록/수정 폼에 검증을 넣는다.

<div class="form-group">
   <label for="itemName" th:text="#{label.item.itemName}">상품명</label>
   <input type="text" id="itemName" th:field="*{itemName}"
          th:errorclass="field-error" class="form-control" placeholder="상품명을 입력해주세요">
   <div class="field-error" th:errors="*{itemName}">상품명 오류</div>
</div>

상품 등록 시 문제가 발생한 경우, 아래와 같이 메시지가 표시된다.

등록 시 오류 메시지

 

 

 

Validator

등록과 수정 기능에 따라 검증을 분리해보자.

 @PostMapping("/item/new")
 public String itemNew(@Login User loginUser, @Validated @ModelAttribute ItemFormDto itemFormDto, BindingResult bindingResult,
                       Model model, @RequestParam("itemImgFile") List<MultipartFile> itemImgFileList, RedirectAttributes redirectAttributes) throws IOException {
...
    • DTO를 전달 받는다. 그리고 @Validated로 검증도 수행하고, BindingResult로 검증 결과도 받는다.
    • 수정의 경우도 등록과 같다. 그리고 폼 객체를 Item 객체로 변환하는 과정을 거친다.
    • Form 전송 객체 분리해서 등록과 수정에 딱 맞는 기능을 구성하고, 검증도 명확히 분리했다.

@ModelAttribute에 지정한 이름으로 Model에 담기게 된다. 뷰 템플릿에서 접근하는 th:object 이름도 같다.

 

 

 

 

 

로그아웃 - 서블릿 HTTP 세션

  • 서블릿을 통해 HttpSession 을 생성하면 다음과 같은 쿠키를 생성한다.
  • 쿠키 이름이 JSESSIONID 이고, 값은 추정 불가능한 랜덤 값이다.

로그아웃 코드는 아래와 같다.
간단히 Session 정보를 가져와 null인지 체크하고 null이 아닐 경우 즉, Session에 값이 있는 경우 session.invalidate();를 실행하도록 해주었다.

    @PostMapping("/logout")
    public String logoutV3(HttpServletRequest request) {
        //세션을 삭제한다
        HttpSession session = request.getSession(false);
        if (session != null) {
            session.invalidate();
        }
        return "redirect:/bookstore/home";
    }
  • request.getSession(false)
    request.getSession()를 사용하면 기본 값이 (create: true) 이므로, 로그인 하지 않을 사용자도 의미없는 세션이 만들어진다. 따라서 세션을 찾아서 사용하는 시점에는 (create: false) 옵션을 사용해서 세션을 생성하지 않아야 한다.
  • session.invalidate()
    보통 로그아웃 같은 기능에서 세션을 삭제하기 때문에 일반적으로 해당 사용자의 모든 세션 데이터를 삭제한다.

 

 

 

 

스프링 인터셉터

스프링 인터셉터도 서블릿 필터와 같이 웹과 관련된 공통 관심 사항을 효과적으로 해결할 수 있는 기술이다.

서블릿 필터가 서블릿이 제공하는 기술이라면, 스프링 인터셉터는 스프링 MVC가 제공하는 기술이다.

  • 로그인한 사용자만 볼 수 있는 페이지들의 접근을 막을 필요가 있다.
  • 로그인 검사 처리를 Spring에서 제공하는 Interceptor를 사용하여 한번에 해결 가능하다.

사용자가 /category/MUSIC 을 요청했을 때, 다시 로그인 폼으로 이동시킨다.

먼저 로그인을 해야 웹서비스를 이용할 수 있다.

로그인 페이지로 돌아온다



 

 

 

 

 

LogInterceptor - 요청 로그 인터셉터

LogInterceptor 코드

LoginCheckInterceptor 코드

 

스프링 인터셉터 - 인증 체크

인터셉터

    • 로그인 여부 체크 인터셉터
    • 인증이라는 것은 컨트롤러 호출 전에만 호출되면 된다. 따라서 preHandle만 구현하면 된다.

 

WebConfig - 인터셉터 등록

WebConfig 코드

기본적으로 모든 경로에 해당 인터셉터를 적용하되 ( /** ), 회원가입( /bookstore/signUp ), 로그인( /bookstore/login ),
로그아웃( /bookstore/logout ), 리소스 조회( /css/** ), ( /error )와 같은 부분은 로그인 체크 인터셉터를 적용하지 않는다.

 

 

 

 

 

ArgumentResolver 활용

 @Login 애노테이션 생성한다.

 

 

@Target(ElementType.PARAMETER) // 파라미터로 사용한다.
@Retention(RetentionPolicy.RUNTIME) // 리플렉션 등을 활용할 수 있도록 런타임까지 애노테이션 정보가 남아있음
public @interface Login {
}

 

LoginUserArgumentResolver 생성한다. LoginUserArgumentResolver 코드

  • supportsParameter() : @Login 애노테이션이 있으면서 User 타입이면 해당 ArgumentResolver가 사용된다.
  • resolveArgument() : 컨트롤러 호출 직전에 호출 되어서 필요한 파라미터 정보를 생성해준다.
    여기서는 세션에 있는 로그인 회원 정보인 User 객체를 찾아서 반환해준다.
    이후 스프링 MVC는 컨트롤러의 메서드를 호출하면서 여기에서 반환된 user 객체를 파라미터에 전달해준다.

WebConfig에 설정 추가한다.

아래와 같이, 사용자가 로그인 상태인지 확인하고 로그인 폼으로 이동시킬 수 있다.

@Login 사용



 

 

 

 

예외 처리와 오류 페이지

스프링 부트는 이런 과정을 모두 기본으로 제공한다. ErrorPage 를 자동으로 등록한다.
이때 /error 라는 경로로 기본 오류 페이지를 설정한다.
오류 뷰 템플릿을 추가하자.
해당 경로 위치에 HTTP 상태 코드 이름의 뷰 파일을 넣어두면 된다.

 

뷰 템플릿이 정적 리소스보다 우선순위가 높고, 404, 500처럼 구체적인 것이 5xx처럼 덜 구체적인 것 보다 우선순위가 높다. 5xx, 4xx 라고 하면 500대, 400대 오류를 처리해준다.

오류 페이지 코드

사용자가 http://localhost:8080/bookstore/home/asdfasd 잘못된 요청을 했을 때,

다음과 같이 오류 페이지를 보여주도록 한다.

오류페이지



 

 

 

후기

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 (인프런 강의)를 많이 참고했었다.

다음에 스프링 시큐리티를 보안(인증과 권한, 인가 등)을 활용한 로그인 구현을 시도해봐야 겠다.

'프로젝트 > 개인 프로젝트 V2' 카테고리의 다른 글

8) 개선사항과 만났던 오류들  (0) 2023.06.14
6) 파일 업로드  (0) 2023.06.14
5) Paging Query  (1) 2023.06.13
4) Enum 활용  (0) 2023.06.13
2) 도메인 모델과 테이블 설계  (0) 2023.06.13

댓글