컨트롤러의 중요한 역할 중 하나는 HTTP 요청이 정상인지 검증하는 것이다.
클라이언트 검증 & 서버 검증
- 클라이언트 검증 : 자바스크립트를 활용한 검증
- 서버 검증 : 요청이 넘어와 컨트롤러나 서버의 다른 부분에서 검증
- 클라이언트 검증은 조작이 가능하여 보안에 취약하다.
- 서버만으로 검증하면, 즉각적인 고객 사용성이 부족해진다.
- 따라서, 이 둘을 적절히 사용하되, 서버 검증은 필수이다.
- API 방식을 사용하면 API 스펙을 잘 정의해서 검증 오류를 API 응답 결과에 잘 남겨주어야 한다.
BindingResult
스프링이 제공하는 검증 오류를 보관하는 객체
검증 오류가 발생하면 BindingResult 객체에 보관하면 된다.
*주의!!
- BindingResult 는 검증할 대상 바로 다음에 와야한다. => 객체의 바인딩 결과를 담고 있기 때문에
즉, @ModelAttribute 객체 바로 다음 순서로 입력해야 한다!
- BindingResult가 있으면 @ModelAttribute에 데이터 바인딩 시, 오류가 발생해도 컨트롤러가 정상적으로 호출된다.
=> 스프링이 오류에 대한 처리를 따로 진행할 것이라고 인식하기 때문.- 예) @ModelAttribute에 바인딩 시 타입 오류가 발생하면? (숫자 입력 필드에 문자 입력)
- BindingResult가 없으면 => 400 오류가 발생하면서 컨트롤러가 호출되지 않고, 오류 페이지로 이동한다.
- BindingResult가 있으면 => 오류 정보(field Error, Object Error)를 bindingResult에 담아서 컨트롤러를 정상적으로 호출한다.
BindingResult에 검증 오류를 적용하는 3가지 방법
- @ModelAttribute 의 객체에 타입 오류 등으로 바인딩이 실패하는 경우, 스프링이 FieldError 생성해서 BindingResult 에 넣어준다.
- 개발자가 직접 넣어준다.
- Validator를 사용한다.
BindingResult와 Errors
- org.springframework.validation.Errors
- org.springframework.validation.BindingResult
BindingResult는 Errors 인터페이스를 상속받는 인터페이스이다.
Errors는 BindingResult에 비해 오류를 처리하는 기능 메서드가 상대적으로 적다. (단순히, 오류 저장과 조회 기능만 제공)
따라서, Errors보다 BindingResult 객체를 주로 사용한다.
FieldError - 필드 오류
필드에 오류가 있으면 FieldError 객체를 생성해서 bindingResult 에 담아두면 된다.
public FieldError(String objectName, String field, String defaultMessage) {}
public FieldError(String objectName, String field, @Nullable Object
rejectedValue, boolean bindingFailure, @Nullable String[] codes, @Nullable
Object[] arguments, @Nullable String defaultMessage) {}
- objectName : @ModelAttribute의 객체 이름
- field : 오류가 발생한 필드 이름
- defaultMessage : 오류 기본 메시지
- rejectedValue : 사용자가 입력한 값(거절된 값)
- bindingFailure : 바인딩 실패(타입 오류)인지를 체크. 타입 오류가 아니면 유효성 검증을 함.
- codes : 메시지 코드
- arguments : 메시지에서 사용하는 파라미터
사용 예시)
if (!StringUtils.hasText(item.getItemName())) {
bindingResult.addError(new FieldError("item", "itemName", "상품 이름은 필수입니
다."));
- 상품명 입력 폼에 데이터가 입력되지 않으면 bindingResult에 FieldError 객체를 생성해서 담는다.
ObjectError - 글로벌 오류
특정 필드를 넘어서는 오류가 있으면 ObjectError 객체를 생성해서 bindingResult 에 담아두면 된다.
public ObjectError(String objectName, String defaultMessage) {}
public ObjectError(String objectName, @Nullable String[] codes, @Nullable Object[] arguments,
@Nullable String defaultMessage) {}
- objectName : @ModelAttribute의 객체 이름
- defaultMessage : 오류 기본 메시지
- codes : 메시지 코드
- arguments : 메시지에서 사용하는 파라미터
사용 예시)
bindingResult.addError(new ObjectError("item", "가격 * 수량의 합은 10,000원 이상이어야
합니다. 현재 값 = " + resultPrice));
- 가격과 수량의 값이 해당 조건에 맞지 않으면, bindingResult에 ObjectError객체를 생성해서 담는다.
타임리프 스프링 검증 오류 통합 기능
타임리프는 스프링의 BindingResult 를 활용해서 편리하게 검증 오류를 표현하는 기능을 제공한다.
- ${ #fields } : #fields 로 BindingResult 가 제공하는 검증 오류에 접근할 수 있다.
- 예) <div th:if="${#fields.hasGlobalErrors()}">
- bindingResult에 글로벌 오류가 존재할 때, 해당 div 출력.
- th : errors : 해당 필드에 오류가 있으면 태그를 출력한다.
- 예) <div class="field-error" th:errors="*{itemName}">
- itemName 필드 값에 오류가 있으면 해당 div를 출력.
- th : errorclass : th : field에서 지정한 필드에 오류가 있으면 class에 errorclass의 값을 추가한다.
- 예) <input th:field="*{itemName}" th:errorclass="field-error" class="form-control" />
- 해당 필드에 오류가 발생하면, 클래스명이 ("form-control" => "form-control field-error ") 변경된다.
오류 발생 시, 입력 값 유지 - FieldError, ObjectError 활용
HTML 폼에서 사용자가 데이터를 잘못 입력하고 서버를 통해 컨트롤러의 @ModelAttribute에 바인딩되는 시점에 오류가 발생하면 model 객체에 데이터를 담을 수 없어 데이터를 유지하기 어렵다.
- 예를 들어, 상품 등록 폼에서 가격 입력란에 숫자가 아닌 문자로 입력하여 서버로 전송하면 가격은 Integer 타입인데 문자로 넘어오게 되어 이 문자를 보관할 방법이 없는 것이다.
이러한 문제를 FieldError, ObjectError를 활용하여 잘못된 값을 유지할 수 있다.
스프링의 바인딩 오류 처리
사용자 잘못 입력 -> BindingResult 검증 -> FieldError(bindingFailure = true) -> FieldError(rejectedValue = 입력한 값) -> 오류 처리와 함께 데이터를 유지한 뷰 반환.
- FieldError의 rejectedValue : 오류 발생 시, 사용자의 입력 값을 저장한다.
- FieldError의 bindingFailure : 타입 오류가 발생했는지를 체크한다.
즉, 바인딩 오류가 나면, FieldError 객체를 생성하여 오류 여부를 체크하고, 해당 데이터 값을 저장하여 이를 BindingResult에 담아서 해당 컨트롤러를 호출하여, 데이터를 유지하고 사용자의 오류메시지를 출력할 수 있게끔 한다.
타임리프의 사용자 입력 값 유지 - th : field
타임리프의 th : field는 데이터 오류가 없을 때는 model 객체의 값을 사용하고, 오류가 발생하면, FieldError에서 보관한 값을 사용하여 데이터 값을 출력한다.
BindingResult - rejectValue(), reject()
BindingResult의 rejectValue(), reject()를 사용하면 앞서 fieldError, ObjectError를 생성하지 않아도 검증 오류를 처리할 수 있다.
rejectValue(), reject()
- rejectValue()
void rejectValue(@Nullable String field, String errorCode,
@Nullable Object[] errorArgs, @Nullable String defaultMessage);
- reject()
void reject(String errorCode, @Nullable Object[] errorArgs, @Nullable String
defaultMessage);
- field : 오류가 발생한 필드이름
- errorCode : 오류코드 (messageResolver를 위한 코드)
- errorArgs : 오류 메시지에서 파라미터(ex. {0})를 치환하기 위한 값
- defaultMessage : 오류 메시지를 찾을 수 없을 때 사용하는 기본 메시지
사용 예시)
// @ModelAttribute Item item
// range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
bindingResult.rejectValue("price", "range", new Object[]{1000, 1000000}, null)
- item.price에서 검증 오류가 발생하면 -> '가격은 1000 ~ 1000000까지 허용합니다'를 출력한다.
- BindingResult는 어떤 객체를 대상으로 검증하는지 target(item)을 이미 알고 있기 때문에 해당 필드명(price)만 작성하면 된다.
'Spring > SpringMVC' 카테고리의 다른 글
검증 - Bean Validation (0) | 2024.01.22 |
---|---|
검증 - Validation 2 (1) | 2024.01.21 |
메시지와 국제화 (1) | 2024.01.11 |
스프링 MVC 기본 기능 2 (0) | 2023.12.18 |
스프링MVC의 기본 기능 (0) | 2023.12.07 |