스프링 부트 - 검증1: Validation

SeungTaek·2021년 8월 20일
0
post-thumbnail

본 게시물은 스스로의 공부를 위한 글입니다.
틀린 내용이 있을 수 있습니다.

📒클라이언트 검증, 서버 검증

  • 클라이언트 검증은 조작할 수 있으므로 보안에 취약하다.
  • 서버만으로 검증하면, 즉각적인 고객 사용성이 부족해진다.
  • 둘을 적절히 섞어서 사용하되, 최종적으로 서버 검증은 필수
  • API 방식을 사용하면 API 스펙을 잘 정의해서 검증 오류를 API 응답 결과에 잘 남겨주어야 함

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

  1. 필드 오류(FieldError)
public String addItem(@ModelAttribute Item item, BindingResult bindingResult) {
 if (!StringUtils.hasText(item.getItemName())) {
 	bindingResult.addError(new FieldError("item", "itemName", "상품 이름은 필수입니다."));
 }
  • BindingResult bindingResult 파라미터의 위치는 반드시 @ModelAttribute Item item 다음에 바로 와야 한다.
  • FieldError 생성자
    • FieldError(String objectName, String field, String defaultMessage)
    • FieldError(검증 객체 이름, 오류가 발생한 필드 이름, 오류 기본 메시지)

  1. 글로벌 오류(ObjectError)
bindingResult.addError(new ObjectError("item", "가격 * 수량의 합은 10,000원 이상이어야 합니다. 현재 값 = " + resultPrice));
  • ObjectError 생성자
    • ObjectError(String objectName, String defaultMessage)
    • ObjectError(@ModelAttribute 이름, 오류 기본 메시지)

📌타임리프에서 오류 보여주기

  1. 필드 오류
<input type="text" th:field="${price}" th:errorclass="field-error" class="form-control" placeholder="가격을 입력하세요">
<div class="field-error" th:errors="*{price}"> 가격 오류 </div>
  • th:errorclass
    • th:field에서 지정한 필드에 오류가 있으면 class정보 추가
  • th:errors
    • 해당 필드에 오류가 있는 경우에 태그 출력.
    • th:if의 편의버전이다.
  1. 글로벌 오류
<div th:if="${#fields.hasGlobalErrors()}">
 	<p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">글로벌 오류 메시지</p>
</div>
  • #fieldsBindingResult가 제공하는 검증 오류에 접근할 수 있다.

📌 타입 오류(바인딩 오류)는 어떻게 검증할까?

  • 컨트롤러에 BindingResult가 인자로 없으면
    • 400오류가 발생하면서 오류 페이지로 이동한다.
  • 컨트롤러에 BindingResult가 인자로 있으면
    • 오류 정보로 fieldError를 만들고 BindingResult에 담아서 컨트롤러를 정상 호출한다.
  • BindingResult에 검증 오류를 적용하는 3가지 방법

    1. @ModelAttribute 의 객체에 타입 오류 등으로 바인딩이 실패하는 경우 스프링이 FieldError 생성해서 BindingResult 에 넣어준다.

    2. 개발자가 직접 넣어준다.

      • 주로 글로벌 오류나 비지니스적인 검증이 이루어진다.
    3. Validator 사용

      • Bean Vaildation과 함께 사용. 다음 게시물에서 알아보자. 사실 거의 대부분 이걸 사용한다.
  • 타입 오류나 비지니스 검증 오류 모두 BindingResult에 들어간다.

📌 사용자가 제출한 오류 값을 그대로 돌려주는 방법

  • FieldError의 또다른 생성자.

    • FieldError(String objectName, String field, @Nullable Object rejectedValue, boolean bindingFailure, @Nullable String[] codes, @Nullable Object[] arguments, @Nullable String defaultMessage)
    • FieldError(오류 발생 객체 이름, 오류 필드, 사용자가 입력한 값(거절된 값), 바인딩실패?, 메시지 코드, 메시지에 이용하는 인자, 기본 메시지)
  • ObjectErrorfield부분만 빼고 동일하다.

  • 이를 통해 사용자가 입력한 오류값이 그대로 남아있게 할 수 있다.(rejectedValue를 통해서 가능.)

  • 바인딩 오류시에는 스프링이 알아서 사용자 입력 값을 담은 FieldError을 생성해 BindingResult에 넣어주니, 크게 신경쓰지 않아도 알아서 해준다.

  • 타임리프의 th:field는 정상 상황에서는 모델 객체 값을 사용하지만, 해당 필드의 오류 발생시 FieldError에 담긴 필드값을 사용한다. 즉, 사용자가 입력한 오류 값을 사용하므로 사용자에게 그대로 보여줄 수 있다.

📒 errors 메시지 파일

  • FieldError, ObjectError의 생성자는 errorCode,arguments를 제공한다. 이것은 오류 발생시 오류 코드로 메시지를 찾기 위해 사용된다.

  • 스프링 부트는 기본 설정으로 src/main/resources/messages.properties에서 메시지를, src/main/resources/ValidationMessages.properties에서 유효성 검사에 대응하는 (에러)메시지를 가져온다.

  • application.propertiesspring.messages.basename=messages,errors추가

## `src/main/resources/errors.properties`에
required.item.itemName=상품 이름은 필수입니다.
range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity=수량은 최대 {0} 까지 허용합니다.
totalPriceMin=가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
  • 오류 코드를 설정해서 오류 메시지 사용하기
bindingResult.addError(new FieldError("item", "price", item.getPrice(), false, new String[]{"range.item.price"}, new Object[]{1000, 1000000}, null))
      
bindingResult.addError(new ObjectError("item", new String[] {"totalPriceMin"}, new Object[]{10000, resultPrice}, null))

📌 error 메시지 생성 규칙

  • FieldError, ObjectError, rejectValue, reject에는 모두 오류 메시지 설정할 수 있는 errorCodeerrorArgs가 있다.
  • errors.properties에 오류 메시지를 만들어두면, 스프링에 이에 맞는 오류 메시지를 찾는다.
  • 오류 메시지는 MessageCodesResolver이 맡는데, 오류 메시지를 찾는 메커니즘을 알아보자

  • 객체 오류

    1. code.objectName
    2. code
    • 예를 들어서...
    • errorCode: required, object name: item라 하면
      1. required.item
      2. required
  • 필드 오류

    1. code . objectName . field
    2. code.field
    3. code.fieldType
    4. code
    • 예를 들어서...
    • errorCode: typeMismatch, object name: "user", field: "age", field type: int 라 하면
      1. typeMismatch.user.age
      2. typeMismatch.age
      3. typeMismatch.int
      4. typeMismatch
    • errorCode: required, object name: "item", field: "itemName", field type: String 이라 하면
      1. required.item.itemName
      2. required.itemName
      3. required.java.lang.String
      4. required

cf 바인딩에 실패하면 BindingReuslttypeMismatch라는 errorCode를 자동으로 넣는다.


📒 조금 더 편하게 검증을 해보자.

  • 사실 BindingResultObject가 뭔지 알고있기 때문에(인자에서 오브젝트 바로 뒤에 쓰기 때문이다) 생략 가능하다.
  • Object를 생략한 rejectValue, rejcet를 사용해보자.
  • rejectValue 생성자(필드 에러)
    • rejectValue(@Nullable String field, String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage);
    • ex bindingResult.rejectValue("price", "range", new Object[]{1000, 1000000}, null)
  • reject 생성자(글로벌 에러)
    • reject(String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage);

사실 스프링에서 검증할땐 거의 대부분 Bean Validation을 사용한다. 다음 게시물에서 자세히 알아보자.


인프런의 '스프링 MVC 2편(김영한)'을 스스로 정리한 글입니다.
자세한 내용은 해당 강의를 참고해주세요

profile
I Think So!

0개의 댓글