검증1 -Validation

KJH·2023년 1월 25일

SpringMVC2

목록 보기
5/12

검증 요구사항

웹 서비스는 폼 입력시 오류가 발생하면, 데이터를 유지한 상태로 오류를 사용자에게 알려주어야 한다.

컨트롤러의 중요한 역활 중 하나는, HTTP 요청이 정상인지 검증하는 것이다

  • 클라이언트 검증(js)은 보안에 취약
  • 서버만으로 검증하면, 고객 사용성 부족
  • 적절히 섞어서 사용하되, 최종 서버 검증은 필수

이전 프로젝트를 일부 수정한 validation-start프로젝트에서 진행


검증 처리

고객이 폼을 잘못 입력한 경우

검증시 오류가 발생하면 errors 해시맵에 담아놓고, 이와함께 폼으로 다시가는식
@ModelAttribute는 사용자가 입력한 데이터를 계속 가지고 있음

폼에서 사용자에게 오류 알리기

 <div th:if="${errors?.containsKey('globalError')}">
 	<p class="field-error" th:text="${errors['globalError']}">전체 오류 메시지</p>
</div>
  • errors? : 에러가 null이면, NPE대신 null 반환
    th:if에서 null을 실패로 처리하고 무시 (SpringEL문법)

BindingResult

스프링이 제공하는 검증 오류를 보관하는 객체

public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult,
  RedirectAttributes redirectAttributes) {

주의
BindingResult bindingResult는 무조건 ModelAttribute Item item 뒤어야함

  • FieldError(String objectName, String field, String defaultmessage)
    • 특정 필드에 오류가 있을 때
    • @ModelAttribute 이름, 필드이름, 오류 기본메시지
  • ObjectError(String objectName, String defaultMessage)
    • 특정 필드를 넘어서는 오류 (글로벌에러)
    • @ModelAttribute 이름, 오류 기본메시지

타임리프를 통한 처리
ObjectError(글로벌에러)

<div th:if="${#fields.hasGlobalErrors()}">
       <p class="field-error"  th:each="err : ${#fields.globalErrors()}" th:text="${err}">전체 오류 메시지</p>
</div>

FieldError
th:field로 설정한 필드와, bindingResult의 필드가 일치할 때 오류

<div>
     <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>
<div>

기능정리

  • #fields : BindingResult가 제공하는 검증 오류에 접근 가능
  • th:errors : 해당 필드에 오류가 있는 경우 태그 출력 th:if편의버전
  • th:errorclass : 해당 필드에 오류가 있으면 클래스정보 추가

타임리프 검증오류 공식 메뉴얼


BindingResult 2

@ModelAttribute에 바인딩 시 오류가 발생하면?

  • BindingResult가 있으면 -> 400오류가 발생하면서 오류페이지로 이동
  • BindingResult가 없으면 ->오류정보(FieldError)를 BindingResult 에 담아서 컨트롤러를 호출

BindingResult에 검증 오류를 적용하는 3가지방법

  • 스프링이 FieldErrorBindingResult에 넣어줌
  • 개발자가 직접 넣어줌
  • Validator사용

BindingResult는 무조건 검증할 대상 다음에 와야하고, 모델에 자동으로 포함된다


FieldError,ObjectError

FieldError 생성자(2개있음)

  • ObjectName : 객체 이름
  • field : 오류 필드
  • rejectedValue : 사용자가 입력한 값(거절된)
  • bindingFailure : 바인딩 실패인지, 검증 실패인지 구분
  • codes : 메시지 코드
  • arguments : 메시지에서 사용하는 인자
  • defaultMessage : 기본 오류 메시지
new FieldError("item","itemName",item.getItemName(), false,null,null,"상품 이름은 필수입니다."

타입 오류로 바인딩에 실패하면
스프링은 FieldError를 생성하면서 입력값을 넣고,
bindingResult에 사용자 입력 데이터를 담아 컨트롤러 호출

타임리프에서 입력 값 유지
th:field="*{price}"
th:field는 정상일땐 모델 객체의 값을 사용하지만, 오류가 발생하면 FieldError에서 보관한 값을 사용해 출력


오류코드와 메시지 처리 1

errors 메시지파일
여기서 했던 메시지처럼, errors.properties라는 별도의 파일 생성 후
spring.messages.basename=messages,errors 추가하면
resources 밑에 있는 errors.properties를 참고하게됨

ex)

if(!StringUtils.hasText(item.getItemName())){
  bindingResult.addError(new FieldError("item","itemName",item.getItemName(), false,
  new String[]{"required.item.itemName"},null,"상품 이름은 필수입니다.")); }

codes에 String 배열로 넣어주면 된다.
배열로 받는 이유는, 첫 없으면 다음 원소를 넣어줘서임
argument가 있으면, Object배열로 넣어주면 된다.


오류코드와 메시지 처리 2

bindingResult는 검증할 객체 바로 다음에 오기 때문에, 검증할 객체를 알고있다.

rejectValue(),reject()

BindingResult가 제공하는 rejectValue(),reject()
FieldError,ObjectError를 직접 생성하지 않고 오류를 다룰 수 있다.

바꾼 방식

bindingResult.rejectValue("itemName","required", 
new Object[]{1000,1000000},null);
////
void rejectValue(@Nullable String field, String errorCode,
@Nullable Object[] errorArgs, @Nullable String defaultMessage);
//오류필드명,messageResolver를 위한 오류 코드, Args,기본메시지

오류 코드와 메시지 처리 3

오류 메시지를 required=필수값 입니다 처럼 단순하게 만들면 범용성이 좋지만, 세밀한 메시지를 작성하기 어렵다.
반대로
required.item.itemName=상품 이름은 필수입니다. 처럼 너무 자세하게 만들면 범용성이 떨어진다.

따라서 범용성으로 사용하다가, 세밀하게 작성해야 하면 세밀한 내용이 적용되도록 단계를 둔다

스프링은 MessageCodesResolver 로 이러한 기능을 지원한다.


오류 코드와 메시지 처리 4

MessageCodesResolver

  • 검증 오류 코드로 코드들을 생성
  • 기본 구현체는 DefaultMessageCodesResolver
  • 주로 ObjectError,FieldError와 사용

DefaultMessageCodesResolver의 기본 메시지 생성 규칙

객체 오류

1.:code+","+object name
2.:code

ex) 에러코드가 totalPriceMin objectName이 item이라면
totalPriceMin.item (1순위) => 없으면 totalPriceMin(2순위)

필드 오류

1.:code+"."+object name + "." + field
2.:code + "." + field
3. code+ "." + field type
4. code

ex) 에러코드가 required objectName이itemName이라면
1. "required.item.itemName"
2. "required.itemName"
3. "required.java.lang.String"
4. "required"

오류 메시지 출력
타임리프가 랜더링 할때 오류가 있다면 th:errors가 생성된 메시지 코드들을 순서대로 돌아가면서 메시지를 찾고 출력 (없으면 default->에러남)

오류 코드와 메시지 처리5

정리
1. rejectValue() 호출
2. MessageCodesResolver를 사용해서 검증 오류 코드로 메시지 코드 생성
3.new FieldError()를 생성하면서 메시지 코드들을 보관
4.th:errors에서 순서대로 메시지를 찾음


오류 코드와 메시지 처리 6

검증 오류 코드는 2가지로 나뉨

  • 개발자가 직접 설정 -> rejectValue() 직접 호출
  • 스프링이 직접 검증 오류에 추가 (주로 타입정보가 안맞음)

스프링은 타입 오류가 발생하면 typeMismatch라는 오류 코드를 생성
=> 이게 MessageCodesResolver를 통하면서 4가지 메시지 코드 생성됨

errors.properties에 메시지 코드를 정의하면 됨
ex)

typeMismatch.java.lang.Integer=숫자를 입력해주세요. 
typeMismatch=타입 오류입니다.

Validator 분리

컨트롤러에서 검증하지않고, 따로 Validator를 만들어서 검증!

스프링에서 제공하는 Validator를 구현
오버라이딩

  • public boolean supports(Class<?? class)
    • return Item.class.isAssignableFrom(clazz);원하는 객체인지
  • `public void validate(Object target, Errors errors)
    • 검증해서 에러를 담아주는 역활
    • object=객체, Errors=bindingResult

추가적인 도움

컨트롤러에서 WebDataBinder를 통해서 검증기를 추가하고
메소드에서 @Validated를 통해서 검증기를 실행할 수 있다.


0개의 댓글