DTO 유효성 검증(Validation)

Backend kwon·2023년 8월 17일

DTO 유효성 검증이 필요한 이유

일반적으로 프론트엔드 쪽 웹 사이트에서는 자바스크립트를 이용해서 사용자가 입력하는 입력 폼 필드의 값에 대해 1차적으로 유효성 검증을 진행한다.

그러나 프론트엔드 쪽에서 유효성 검사에 통과되었다고 그 값이 반드시 유효한 값이라고 보장할 수 없다.

특히나 자바스크립트로 전송되는 데이터는 브라우저의 개발자 도구를 사용해서 브레이크포인트(breakpoint)를 추가한 뒤에 얼마든지 그 값을 조작할 수 있기 때문에 프론트엔드 쪽에서 유효성 검사를 진행했다고 하더라도 서버 쪽에서 한번 더 유효성 검사를 진행해야 된다.

프론트엔드 쪽에서 진행하는 유효성 검사는 사용자 편의성 때문에 진행한다고 생각하면 편하다.

 

DTO 클래스에 유효성 검증 적용

유효성 검증을 위한 의존 라이브러리 추가
build.gradle 파일의 dependencies 항목에 'org.springframework.boot:spring-boot-starter-validation’을 추가

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	...
	...
}

MemberPostDto 유효성 검증 예시

public class MemberPostDto {
    @NotBlank
    @Email
    private String email;

    @NotBlank(message = "이름은 공백이 아니어야 합니다.")
    private String name;

    @Pattern(regexp = "^010-\\d{3,4}-\\d{4}$",
            message = "휴대폰 번호는 010으로 시작하는 11자리 숫자와 '-'로 구성되어야 합니다.")
    private String phone;
                           .
                           .
   
  • @NotBlank
    비어있지 않은지 검증.
    null값이나 공백, 스페이스 같은 값들을 모두 허용하지 않는다.
    유효성 검증에 실패하면 에러메시지가 콘솔에 출력.

  • @Email
    유효한 이메일 주소인지를 검증
    유효성 검증에 실패하면 내장된 디폴트 에러 메시지가 콘솔에 출력

  • @Pattern
    정규표현식에 매치되는 유효한 것인지를 검증
    유효성 검증에 실패하면 내장된 디폴트 에러 미시지가 콘솔에 출력

(message 애트리뷰트를 지정하면, 지정한 문자열이 에러 메시지로 콘솔에 출력)

 

qwe123@abcd와 같이 gmail.com 같은 이메일 주소 형식이 아닌데도 유효성 검증에 통과하는 이유

-> gmail.com과 같은 이메일 주소에서 gmail.com 은 도메인 네임(Domain Name)을 의미한다.
그 중에서 .com은 최상위 도메인(TLD, Top-Level Domain)을 의미하는데 이메일 주소의 스펙(사양, Specification)을 확인해 보면 최상위 도메인이 없는 이메일 주소의 경우도 때로는 정상 이메일 주소로 허용을 하는 것을 확인할 수 있다.

따라서 만약 현실적으로 최상위 도메인까지 포함되어야 유효한 이메일 주소라고 판단하고 싶은 경우에는 정규 표현식을 이용해 조금 더 세밀한 유효성 검사 조건을 지정할 수 있다.

 

@PostMapping
    public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
        return new ResponseEntity<>(memberDto, HttpStatus.CREATED);
    }
		...

유효성 검증 애너테이션이 추가된 DTO 클래스에서 유효성 검증 로직이 실행되게 하기 위해서는 @Valid 애너테이션을 추가해야 한다.

 

정규표현식

다양한 조건을 선택적으로 검증하고자 할 때 유용한 방법 중 하나가 바로 정규 표현식(Reqular Experssion)이다.

    @Pattern(regexp = "^\\S+(\\s?\\S+)*$", message = "회원 이름은 공백이 아니어야 합니다.")
    private String name;

    @Pattern(regexp = "^010-\\d{3,4}-\\d{4}$",
            message = "휴대폰 번호는 010으로 시작하는 11자리 숫자와 '-'로 구성되어야 합니다.")
    private String phone;

name 멤버 변수에 사용한 “^\S+(\s?\S+)*$” 정규 표현식에서

‘^’은 문자열의 시작을 의미한다.
‘$’는 문자열의 끝을 의미한다.
’는 ‘’ 앞에 평가할 대상이 0개 또는 1개 이상인지를 평가한다.
‘\s’는 공백 문자열을 의미한다.
‘\S’ 공백 문자열이 아닌 나머지 문자열을 의미한다.
‘?’는 ‘?’ 앞에 평가할 대상이 0개 또는 1개인지를 의미한다.
‘+’는 ‘+’ 앞에 평가할 대상이 1개인지를 의미한다.

 

쿼리 파라미터(Query Parameter 또는 Query String) 및 @Pathvariable에 대한 유효성 검증

지금까지 Request Body의 유효성 검사를 진행하는 방법을 알아보았는데, 검증 대상에서 빠진 항목이 하나 있다.

그것은 바로 @PathVariable("member-id") long memberId 변수이다.

일반적으로 수정이 필요한 데이터의 식별자는 0 이상의 숫자로 표현을 한다.

@RestController
@RequestMapping("/v1/members")
@Validated   // (1)
public class MemberController {
		...

    @PatchMapping("/{member-id}")
    public ResponseEntity patchMember(@PathVariable("member-id") @Min(1) long memberId,
                                    @Valid @RequestBody MemberPatchDto memberPatchDto) {
                                    .
                                    .
                                    .
                                   

@PathVariable("member-id") long memberId에 1 이상의 숫자일 경우에만 유효성 검증에 통과하도록 @Min(1)이라는 검증 애너테이션을 추가했다.

@PathVariable이 추가된 변수에 유효성 검증이 정상적으로 수행되려면 (1)과 같이 클래스 레벨에 @Validated 애너테이션을 반드시 붙여주어야 한다.

 

Custom Validator를 사용한 유효성 검증

절차는 다음과 같다.
1.Custom Validator를 사용하기 위한 Custom Annotation을 정의한다.
2.정의한 Custom Annotation에 바인딩되는 Custom Validator를 구현한다.
3.유효성 검증이 필요한 DTO 클래스의 멤버 변수에 Custom Annotation을 추가한다

  1. Custom Annotaion 정의
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {NotSpaceValidator.class}) // (1)
public @interface NotSpace {
    String message() default "공백이 아니어야 합니다"; // (2)
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
  1. Custom Validator 구현
public class NotSpaceValidator implements ConstraintValidator<NotSpace, String> {

    @Override
    public void initialize(NotSpace constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value == null || StringUtils.hasText(value);
    }
}
  1. 유효성 검증을 위해 Custom Annotation 추가
public class MemberPatchDto {
    private long memberId;

    @NotSpace(message = "회원 이름은 공백이 아니어야 합니다") // (1)
    private String name;
profile
백엔드개발자를 향해서

0개의 댓글