본문 바로가기

Spring Boot

[Spring Boot] Validation 유효성 검사

728x90

유효성 검증의 필요성

클라이언트가 회원가입 요청을 보낼 때, 서버는 사용자의 이름, 나이, 이메일, 아이디, 비밀번호, 생일, 가입 날짜 등이 올바른 형식인지 확인해야 합니다. 예를 들어:

  • 이름을 입력하지 않았을 때는?
  • 나이가 -10살이나 200살일 때는?
  • 비밀번호가 "123"처럼 보안에 취약할 때는?

이러한 검증을 if 문을 통해 하나하나 확인할 수 있지만, 검증할 항목이 많아질수록 코드의 길이는 길어질 수밖에 없습니다. 각 항목에 대해 개별적으로 검증하면 코드가 길어지고 중요한 서비스 로직이 누락될 수 있습니다.

Spring Boot에서 유효성 검증

이러한 문제를 쉽게 해결하기 위해 Spring Boot에서는 spring-boot-starter-validation 의존성을 사용할 수 있습니다.

 

Validation 2.0 스펙

https://beanvalidation.org/2.0/spec/#builtinconstraints

 

Jakarta Bean Validation specification

BeanNode, PropertyNode and ContainerElementNode host getContainerClass() and getTypeArgumentIndex(). If the node represents an element that is contained in a container such as Optional, List or Map, the former returns the declared type of the container and

beanvalidation.org

 

 

  1. Validation을 사용하는 이유
    1. 유효성 검증 코드의 길이 감소: 유효성 검증을 별도로 처리하여 서비스 로직을 간결하게 유지할 수 있습니다.
    2. 일관성 유지: 여러 명이 코딩할 때, 각자의 스타일에 따라 유효성 검증을 하게 되면 일관성을 유지하기 어렵습니다.
    3. 유지 보수 용이성: 검증 로직이 변경될 때, 테스트 코드 등 전체 로직에 미치는 영향을 최소화할 수 있습니다.

자주 사용하는 Validation 어노테이션

  • @Size: 문자열의 길이 검증 (정수 타입에는 사용 불가)
  • @NotNull: null 불가
  • @NotEmpty: null과 빈 문자열 불가 (" "는 가능)
  • @NotBlank: null, 빈 문자열, 공백 문자열 불가
  • @Pattern: 정규식 사용
  • @Max: 최대값 (정수 타입)
  • @Min: 최소값 (정수 타입)
  • @AssertTrue/False: 논리값 검증
  • @Valid: 객체 내부의 validation 실행
  • @Past: 과거 날짜
  • @PastOrPresent: 과거 또는 오늘 날짜
  • @Future: 미래 날짜
  • @FutureOrPresent: 미래 또는 오늘 날짜

 

간단한 예제: 학생의 정보와 점수 받기

Student 모델 만들기

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;


@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Student {

    private String id;

    private String password;

    private String email;

    private String name;

    private Integer score;
}

 

기본 Post 컨트롤러 생성

import lombok.extern.slf4j.Slf4j;
import org.example.model.Student;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
@RequestMapping("/student/info")
public class StudentValidationController {

    @PostMapping("")
    public Student studentInfo(
            @RequestBody
            Student student
    ) {
        log.info("info : {}", student);

        return student;
    }
}

 

유효성 검사를 하지 않았을 때

{
    "id": " ",
    "password": "",
    "name": "홍길동",
    "email": "hong.com",
    "score": 1000
}

 

 

아이디와 비밀번호는 입력하지 않았고, 이메일도 형식에 맞지 않습니다.

점수도 100점을 넘길 수 없는데 1000점으로 잘못 입력되어 있지만

유효성 검사를 하지 않았기 때문에 오류가 발생하지 않습니다.

 

Validation을 사용해보기

import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;


@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Student {

    @NotBlank	// null, "", " " 를 허용하지 않음
    @Size(min = 3, max = 10)	// 문자열 크기 3~10
    private String id;

    @NotBlank
    @Size(min = 4, max = 12)
    private String password;

    @Email		// email 형식인지
    private String email;

    @NotBlank
    private String name;

    @Min(1) @Max(100)  // 정수형은 Min, Max 사용
    private Integer score;
}
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.example.model.Student;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
@RequestMapping("/student/info")
public class StudentValidationController {

    @PostMapping("")
    public Student studentInfo(
            @Valid // Valid 어노테이션 추가
            @RequestBody
            Student student
    ) {
        log.info("info : {}", student);

        return student;
    }
}

 

유효성 검사를 적용한 후

이렇게 했을 때 똑같은 데이터를 보내면:

2024-07-04T16:18:04.614+09:00  WARN 8584 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.example.model.Student org.example.controller.StudentValidationController.studentInfo(org.example.model.Student) 
with 6 errors: [Field error in object 'student' on field 'id': rejected value [ ]; codes [Size.student.id,Size.id,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.id,id]; arguments []; default message [id],10,3]; default message [크기가 3에서 10 사이여야 합다]]

[Field error in object 'student' on field 'email': rejected value [hong.com]; codes [Email.student.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.email,email]; arguments []; default message [email],[Ljakarta.validation.constraints.Pattern$Flag;@3cd0ea2a,.*]; default message [올바른 형식의 이메일 주소여야 합니다]]
[Field error in object 'student' on field 'password': rejected value []; codes [Size.student.password,Size.password,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.password,password]; arguments []; default message [password],12,4]; default message [크기가 4에서 12 사이여야 합니다]] 
[Field error in object 'student' on field 'password': rejected value []; codes [NotBlank.student.password,NotBlank.password,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.password,password]; arguments []; default message [password]]; default message [공백일 수 없습니다]] 
[Field error in object 'student' on field 'score': rejected value [1000]; codes [Max.student.score,Max.score,Max.java.lang.Integer,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.score,score]; arguments []; default message [score],100]; default message [100 이하여야 합니다]] 
[Field error in object 'student' on field 'id': rejected value [ ]; codes [NotBlank.student.id,NotBlank.id,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.id,id]; arguments []; default message [id]]; default message [공백일 수 없습니다]] ]

 

 

이렇게 에러가 나오는 것을 확인할 수 있습니다.

클라이언트에게 문제를 알리는 방법

현재 코드는 서버가 어떤 문제인지 알 수 있지만, 클라이언트는 400 Bad Request만 알 수 있습니다.

즉, 클라이언트는 어떤 문제인지 파악할 수 없습니다.

클라이언트에게 상세한 오류 메시지를 전송하는 방법은 다른 글에서 정리하겠습니다

728x90