Validation

폼에 잘못된 값을 입력하면 친절하게 알려줘야 한다. HTTP 요청이 정상인지 검증하는 것도 컨트롤러의 중요한 역할 중 하나다.

  • 클라이언트 검증은 조작할 수 있어 보안에 취약하다.

    • 자바스크립트로 검증 로직을 잘 만들어도 포스트맨으로 값 바꿔서 넘겨버릴 수도 있다.

  • 그렇다고 서버만으로 검증하면 고객 사용성이 좋지 않다.

    • 클라이언트에서 창을 바로 띄워주면 사용성이 좋아질 수 있다.

  • 둘을 적절히 섞어 사용하되 최종적으로 서버 검증은 필수다.

  • API 방식을 사용하면 API 스펙을 잘 정의해 검증 오류를 API 응답 결과에 잘 남겨줘야 한다.

폼에 정상 데이터를 입력하면 서버에서는 검증 로직을 통과하고, 상품을 저장하고, 상세 화면으로 redirect 한다.

폼에 값을 상품명을 입력하지 않거나, 가격이나 수량이 너무 작거나 크면 서버 검증 로직이 실패해야 한다.

이렇게 검증에 실패한 경우 고객에게 다시 상품 등록 폼을 보여주고 어떤 값이 잘못됐는지 알려줘야 한다.

ValidationItemControllerV1

@Slf4j
@Controller
@RequestMapping("/validation/v1/items")
@RequiredArgsConstructor
public class ValidationItemControllerV1 {

    @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() > 1_000_000) {
            errors.put("price", "가격은 1,000원에서 1,000,000원까지 허용합니다.");
        }

        if (item.getQuantity() == null || item.getQuantity() >= 9999) {
            errors.put("quantity", "수량은 최대 9,999까지 허용합니다.");
        }

        if (item.getPrice() != null && item.getQuantity() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();

            if (resultPrice < 10000) {
                // 여러 필드에 대한 오류는 필드 이름을 넣을 수 없으니 globalError로 넣는다.
                errors.put("globalError", "가격 * 수량은 10,000원 이상이어야 합니다.");
            }
        }

        // 에러가 있다면 입력 폼으로 다시 돌아간다.
        if (!errors.isEmpty()) {
            log.info("errors = {}", errors);
            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}";
    }
}
  • 검증 로직을 구현 후 에러가 있으면 Map에 담고 입력 폼으로 다시 돌려보낸다.

남은 문제점

  • 뷰 템플릿에 중복 처리가 많다.

  • 타입 오류 처리가 안된다.

    • 숫자 타입에 문자가 들어오면 오류가 발생한다.

    • 이 오류는 스프링 MVC에서 컨트롤러에 들어오기도 전에 발생하는 예외이기 때문에 컨트롤러가 호출되지 않고 400 에러가 발생한다.

  • 따라서 고객이 입력한 값도 별도로 관리가 되어야 한다.

    • 만약 컨트롤러가 호출되더라도 Integer이므로 문자를 보관할 수 없다.

    • 바인딩이 불가하므로 입력한 문자가 사라지고 어떤 내용을 입력해서 오류가 발생했는지 고객 입장에서 이해하기 어렵다.

Last updated