Back-End/Spring

[Spring MVC2][검증(Validation)] - 직접 검증

얄루몬 2022. 4. 25. 11:15

💻본 포스팅은 '스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 김영한'님의 강의를 듣고 작성되었습니다.

https://inf.run/vQHp

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com


[검증]

  • 문제 상황
    • 사용자가 Form에 넣은 데이터가 우리가 원하지 않는 값을 넣었을 때 이를 '400' 에러 페이지를 보여주게 되면 사용자 입장에선 다시 입력 페이지로 들어가야 하는 것은 매우 귀찮은 일이 될 것이다. 
  • 문제 해결
    • 이를 해결하기 위해서는 400 에러 페이지로 넘어가지 않게하고 아닌 우리가 어떤 부분에서 값을 잘못 넣었는지를 화면에서 보여주어야 한다.
  • 즉 검증이란 들어온 데이터가 유효한 값인지(우리가 설정한 범위 내의 값을 의미..)를 확인하는 것을 의미하며 우리는 검증을 직접 검증, 어노테이션을 이용한 검증, 인터페이스 구현을 통한 검증 등의 방식으로 진행할 수 있다.

 

[직접 검증하는 방식]

[controller]

    @PostMapping("/add")
    public String addItem(@ModelAttribute Item item, RedirectAttributes redirectAttributes, Model model) {

        //검정 오류 결과를 보관
        Map<String, String> errors = new HashMap<>();

        //검증 로직(특정 필드 검증)
        if (!StringUtils.hasText(item.getItemName())){
            errors.put("itemName", "상품 이름은 필수입니다.");
        }

        if (item.getPrice() == null || item.getPrice() <1000 ||item.getPrice() > 1000000){
            errors.put("price","가격은 1,000원에서 1,000,000원 사이를 넣어주세요.");
        }

        if (item.getQuantity() == null || item.getQuantity() >= 9999){
            errors.put("quantity","수량은 최소 1개부터 최대 9,999개까지 넣을 수 있습니다.");
        }

        //특정 필드가 아닌 복합 룰 검증
        if (item.getPrice() != null && item.getQuantity() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();

            if (resultPrice < 10000) {
                errors.put("globalError", "가격 * 수량의 합은 10,000원 이상이어야 합니다. 현재 값 = " + resultPrice);
            }
        }

        //검증 실패 시 다시 입력 폼으로
        if (!errors.isEmpty()){
            model.addAttribute("errors", errors);
            return "validation/v1/addForm";
        }

        //성공 로직
        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("status", true);
        return "redirect:/validation/v1/items/{itemId}";
    }
  • Controller를 사용해서 HTTP 요청이 정상인지를 확인하는 작업이다
  • Controller의 중요한 기능 중 하나는 HTTP 요청이 정상인지를 확인하는 것에 있다.
    • 이때 검증 로직은 매우 어렵다

[html 페이지]

    <form action="item.html" th:action th:object="${item}" method="post">

        <!--오류 메시지 출력 부분-->
        <div th:if ="${errors?.containsKey('globalError')}">
            <p class = "field-error" th:text ="${errors['globalError']}">전체 오류 메시지</p>
        </div>

        <div>
            <label for="itemName" th:text="#{label.item.itemName}">상품명</label>
            <input type="text" id="itemName" th:field="*{itemName}"
                   th:class="${errors?.containsKey('itemName')}? 'form-control field-error':'form-control'"
                   class="form-control" placeholder="이름을 입력하세요">

            <div class="field-error" th:if="${errors?.containsKey('itemName')}" th:text="${errors['itemName']}">
                상품명 오류
            </div>

        </div>
        <div>
            <label for="price" th:text="#{label.item.price}">가격</label>
            <input type="text" id="price" th:field="*{price}"
                   th:class="${errors?.containsKey('price')} ? 'form-control field-error' : 'form-control'"
                   class="form-control"
                   placeholder="가격을 입력하세요">
            <div class="field-error" th:if="${errors?.containsKey('price')}" th:text="${errors['price']}">
                가격 오류
            </div>
        </div>

        <div>
            <label for="quantity" th:text="#{label.item.quantity}">수량</label>
            <input type="text" id="quantity" th:field="*{quantity}"
                   th:class="${errors?.containsKey('quantity')} ? 'form-control field-error' : 'form-control'"
                   class="form-control"
                   placeholder="수량을 입력하세요">
            <div class="field-error" th:if="${errors?.containsKey('quantity')}" th:text="${errors['quantity']}">
                수량 오류
            </div>

        </div>
  • ?는 null값이 아닐 땐 무시한다는 문법이다. (null값이 아니면 저 테그는 무시된다.)
  • 에러 값을 모아둔 곳에 해당 에러가 들어있다면 그에 맞게 메시지를 보여주며 수정을 할 수 있게 한다.

[검증 처리 성공시]

[검증 처리 실패시]

 

[클라이언트, 서버 검증]

  • 클라이언트 검증은 JS를 이용해서 진행하기에 조작이 가능해 보안에 취약하다.
  • 서버 검증은 서버만 검증하게 될 때 즉각적인 고객 사용성이 부족해진다.(즉각적으로 보이지 않으니 효과가 떨어진다는 의미다.)
  • 둘을 적적히 잘 섞어 사용하되 최종적으로 서버 검증은 필수다.
  • API 방식을 사용해 API 스펙을 잘 정의해 검증 오류를 API 응답 결과에 잘 남겨주어야 한다.

 

[직접 검증의 장점과 단점]

  • 장점
    • 검증 오류 발생 시 '400' 에러 페이지로 넘어가지 않아 다시 돌아가야 하는 번거로움이 줄어든다.
    • 검증 오류가 발생해도 고객이 입력한 데이터가 유지된다.
    • 검증 오류를 고객에게 친절하게 안내해서 다시 입력할 수 있게 한다.
  • 직접 검증의 단점
    • 뷰 템플릿의 중복 처리가 많다.(위의 코드만 봐도 중복되는 부분이 많음을 알 수 있다.)
    • 타입 오류 처리가 안 된다. 
      • 타입이 안 맞으면 데이터를 안 받아서 컨트롤러 호출이 안 된다;;;
      • 그렇게 되면 '400' 에러 페이지로 넘어가버리게 된다.
    • 고객이 입력한 값도 어딘가에 별도 관리해야 한다.
      • 타입 오류가 발생할 때 고객이 입력한 문자는 바인딩 불가해져 사라지고 고객은 본인이 어떤 내용을 입력해서 오류 발생을 시켰는지 이해하기도 어려워지게 된다. 
  • 해결 방안
    • 추가적인 방법이 더 필요하기에 다음 포스팅에서는 이런 문제를 해결하도록 하자. 
    • 스프링이 이를 해결하는 많은 방식을 제공하고 있다!!