Validation(검증)은 클라이언트가 입력한 데이터가 서버에서 처리되기 전에 올바르고 유효한 값인지 확인하는 과정을 말합니다. 웹 애플리케이션에서는 데이터의 신뢰성과 보안을 유지하기 위해 필수적인 과정입니다.
Spring은 javax.validation과 Hibernate Validator(Bean Validation의 구현체)를 통해 유효성 검사를 지원합니다. 이를 사용하면 애노테이션 기반으로 간편하게 데이터를 검증할 수 있습니다.
@NotNull: 값이 null이면 안 됨 @NotBlank: 공백 문자만으로 이루어진 값은 안 됨 @Size: 문자열, 배열 등의 길이를 제한 @Min, @Max: 숫자 값의 최소/최대값 제한 @Email: 올바른 이메일 형식인지 확인 @Pattern: 정규식을 이용한 값 검증 Spring에서 Validation은 주로 DTO(Data Transfer Object)에 설정됩니다.
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class UserRequestDTO {
@NotBlank(message = "닉네임은 필수 입력 항목입니다.")
@Size(max = 20, message = "닉네임은 20자를 초과할 수 없습니다.")
private String nickname;
@Email(message = "유효한 이메일 형식이 아닙니다.")
private String email;
@NotBlank(message = "비밀번호는 필수 입력 항목입니다.")
@Size(min = 8, max = 20, message = "비밀번호는 8자 이상 20자 이하여야 합니다.")
private String password;
// Getters and Setters
}
컨트롤러에서 @Valid를 사용하여 DTO 검증을 수행합니다.
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping("/register")
public ResponseEntity<String> registerUser(@Valid @RequestBody UserRequestDTO userRequestDTO) {
return ResponseEntity.ok("회원가입 성공!");
}
}
Spring은 검증 실패 시 자동으로 MethodArgumentNotValidException을 발생시킵니다. 이를 커스터마이징하여 사용자에게 적절한 에러 메시지를 반환할 수 있습니다.
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error -> {
errors.put(error.getField(), error.getDefaultMessage());
});
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
Validation 애노테이션으로 해결할 수 없는 경우, 커스텀 Validator를 만들어 비즈니스 로직을 검증할 수 있습니다.
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy = PasswordValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPassword {
String message() default "비밀번호는 숫자와 특수문자를 포함해야 합니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return false;
}
// 숫자와 특수문자를 포함해야 유효
return value.matches(".*\\d.*") && value.matches(".*[@#$%^&+=].*");
}
}
@ValidPassword
private String password;