웹 개발을 하다 보면, 사용자로부터 받은 데이터의 유효성을 검증하는 것이 얼마나 중요한지 알 수 있습니다.
이번에는 Spring Boot에서 API 요청 시 입력값 형식을 검증하는 방법과 주의 할 점에 대해 알아보겠습니다.
사용자의 입력값은 프론트와 서버 모두에서 체크하는 것이 중요합니다.
저도 처음 웹 개발을 할 때는, ‘입력값에 대한 유효성 검사를 프론트 측에서만 하면 서버에서는 괜찮겠지’ 라고 생각했었습니다.
하지만 프론트와 서버에서 하는 입력값 형식 검증은 조금 다른 의미가 있습니다.
프론트에서 하는 검증: 잘못된 형식의 데이터를 입력하는 것에 대한 체크 (UX 향상을 위한 첫 번째 단계)
서버에서 하는 검증: 유효하지 않은 데이터에 대한 체크 (시스템의 안전성과 데이터의 무결성 보장)
결론적으로, 프론트 측의 검증은 사용자가 실수로 잘못된 형식의 데이터를 입력하는 것을 방지하는 것이 목적이고, 서버 검증은 악의적인 공격으로부터 시스템을 보호하고 데이터의 무결성을 유지하는데 목적이 있습니다.
만약 서버에서 검증 절차가 없다면, 사용자가 악의적으로 공격 코드를 포함한 데이터를 올바른 형식으로 입력했을 때, 별다른 방어 방법이 없습니다.
Spring Boot에서 Validation API를 사용하여 입력값의 유효성 검사를 수행하는 것은 매우 중요합니다. 이를 통해 데이터의 정확성을 보장하고, 애플리케이션의 안정성을 향상시킬 수 있습니다.
아래는 각각 회원가입과 공지사항 등록 요청에 대해 Validation을 이용하여 유효성 검증에 대한 예시 코드입니다.
여기서 사용된 어노테이션들에 대해 자세히 설명드리겠습니다.
@Getter
public class MemberJoinRequest {
@NotBlank(message = "이메일은 필수 입력값입니다.")
@Email(message = "이메일 형식이 올바르지 않습니다.")
private String email;
@NotBlank(message = "닉네임은 필수 입력값입니다.")
@Size(max = 8, message = "닉네임은 8자 이하로 입력해야 합니다.")
private String nickname;
@NotBlank(message = "비밀번호는 필수 입력값입니다.")
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&]).{8,20}$", message = "비밀번호는 8~20자의 영문 대소문자, 숫자, 특수문자를 포함해야 합니다.")
private String password;
@NotBlank(message = "비밀번호 확인은 필수입니다.")
private String passwordConfirm;
}
@NotBlank
해당 필드가 null이 아니며, 빈 문자열이나 공백만 있는 문자열이 아님을 나타냅니다.
예를 들어, 이메일, 닉네임, 비밀번호, 비밀번호 확인 필드에 사용되어 이들이 반드시 입력되어야 할 때 사용합니다.
해당 문자열이 이메일 형식에 맞는지 검증합니다.
이메일 필드에 사용되어 사용자가 올바른 이메일 형식으로 입력했는지 확인합니다.
@Size
문자열의 길이나 컬렉션의 크기가 지정된 범위 내에 있는지 검증합니다.
닉네임 필드에 사용되어 최대 8자까지 입력할 수 있음을 나타냅니다.
@Pattern
문자열이 정규 표현식과 일치하는지 검증합니다.
비밀번호 필드에 사용되어 비밀번호가 영문 대소문자, 숫자, 특수문자를 포함하는 8~20자의 규칙을 만족하는지 확인합니다.
@Getter
public class NoticeRegisterRequest {
@NotBlank(message = "제목은 필수 입력값입니다.")
private String title;
@NotBlank(message = "내용은 필수 입력값입니다.")
private String content;
@NotNull(message = "공지 시작 일시는 필수 입력값입니다.")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
private LocalDateTime startDateTime;
@NotNull(message = "공지 종료 일시는 필수 입력값입니다.")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
private LocalDateTime endDateTime;
private List<MultipartFile> files;
}
@NotNull
해당 필드 값이 null이 아니어야 함을 나타냅니다.공지 시작 일시와 공지 종료 일시 필드에 사용되어, 이들 값이 반드시 있어야 함을 나타냅니다.
@DateTimeFormat
날짜 및 시간을 특정 형식으로 파싱하거나 표시할 때 사용됩니다.
공지 시작 일시와 공지 종료 일시 필드에 사용되어, "yyyy-MM-dd HH:mm" 형식의 날짜 및 시간 데이터가 필요함을 명시합니다.
@NotNull
: 값이 null이 아님을 검증합니다.공백(”“)이나 빈 값(” ”)도 허용
@NotEmpty
: 문자열, 컬렉션, 배열이 null이 아니며, 또한 비어있지 않음을 검증합니다.공백(”“) 허용하지 않고, 빈 값(” ”) 허용
@NotBlank
: 문자열이 null이 아니며, 공백을 제외한 문자를 하나 이상 포함하고 있음을 검증합니다.공백(”“)과 빈 값(” ”)모두 허용 하지 않음
위에서 작성한 Request Dto를 Controller에서 @Valid
어노테이션과 함께 선언해주시면 됩니다.
@PostMapping("/join")
public Response<Void> join(@ModelAttribute @Valid MemberJoinRequest request) {
memberService.join(request);
return Response.success(HttpStatus.CREATED, "회원 가입 성공!");
}
이때 필드 각각의 유효성 검사에 대한 에러 메시지를 클라이언트에게 함께 반환하고 싶으면, 아래와 같은 @RestControllerAdvice
를 사용하여 예외 메시지를 리스트에 담아 반환하도록 예외 핸들러를 구현하시면 됩니다.
@RestControllerAdvice
public class GlobalExceptionAdvice {
// MethodArgumentNotValidException 예외 핸들러
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<Response<List<String>>> handleValidationExceptions(MethodArgumentNotValidException e) {
List<String> errors = new ArrayList<>(); // 에러 메시지를 담을 리스트
e.getBindingResult().getAllErrors().forEach((error) -> {
String errorMessage = error.getDefaultMessage();
errors.add(errorMessage);
});
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(Response.fail(HttpStatus.BAD_REQUEST, "입력 데이터 형식 오류입니다.", errors)); // 리스트를 함께 바디로 반환
}
}
그럼 아래와 같이 각 필드에 대한 유효성 검사의 해당 에러 메시지를 Response Body에 담아 반환할 수 있습니다.
{
"code": 400,
"message": "입력 데이터 형식 오류입니다.",
"data": [
"공지 종료 일시는 필수 입력값입니다.",
"제목은 필수 입력값입니다.",
"공지 시작 일시는 필수 입력값입니다.",
"내용은 필수 입력값입니다."
]
}
Spring Boot의 Validation을 사용하여 애플리케이션에서 사용자 입력값의 유효성을 쉽게 검증할 수 있습니다.
이는 데이터의 정확성을 보장하고, 애플리케이션의 안정성을 향상시킬 수 있습니다.