본문 바로가기

Web Sever 개발과 CS 기초/스프링

Validation 사용하여 Respuset 정보 검증하기

관련 내용

해당 프로젝트 깃허브

[백엔드/스프링] - org.passay 사용하여 Password Validation 검증기 만들기

개요 목적

클라이언트 Request DTO를 검증해야 하는 경우가 있다. 예를 들어 회원가입 시 아이디나 비밀번호 형식이 올바른 지 확인할 때가 그렇다.

이럴 때 간단하게 검증 하는 방법인 Validation에 대해서 알아보고,

실제 작성 코드(회원가입 로직 검증)와

어떤 것을 검증할 수 있는 지 Validation 종류를 알아보자.

Validation 먼저 이해하기

Bean Validation은 애노테이션 하나로 검증 로직을 매우 편리하게 도와주는 기술이다.

Bean Validation은 특정한 구현체가 아니라 Bean Validation 2.0(JSR-380)이라는 기술 표준이다. 구현체 중에서 일반적으로 사용하는 것은 하이버네이트 Validator가 있다.

사용 방법은 아래 의존성을 먼저 추가하고,

implementation 'org.springframework.boot:spring-boot-starter-validation'

컨트롤러에 메소드에 @Valid 또는 @Validated를 붙여 검증하고 싶은 RequestDto를 지정한다.

지정한 형식과 다른 RequestDto가 들어오면, 예외를 발생해서 검증을 하게된다.

@Valid의 동작 원리

먼저 클라이언트 요청은 디스패처 서블릿을 통해 컨트롤러로 전달된다.

전달 과정에서 컨트롤러 메소드의 객체를 만들어주는 ArgumentResolver가 동작하는데, @Valid도 ArgumentResolver에 의해 처리된다.

대표적으로 @RequestBody는 Json 메세지를 객체로 변환해주는 작업이 ArgumentResolver의 구현체인 RequestResponseBodyMethodProcessor가 처리하며, 이 내부에서 @Valid로 시작하는 어노테이션이 있을 경우에 유효성 검사를 진행한다.

그리고 검증에 오류가 있다면 MethodArgumentNotValidException 예외가 발생하여 잘못된 형식 데이터를 찾아낸다.

@Validated의 동작 원리

ArgumnetResolver에 의해 유효성 검사가 진행되었던 @Valid와 달리, @Validated는 AOP 기반으로 메소드 요청을 인터셉터하여 처리된다.

Validation으로 회원 가입 요청 검증하기

먼저 검증할 DTO 생성하기

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RequestGeneralMember {
		
    //영문과 숫자를 조합한 입력인지 검증하기
    @Pattern(regexp = "^[a-z0-9]{8,20}$", message = Constants.joinLoginNameValidation)
    private String loginName;
    
    //pssay라이브러리를 사용한 직접 만든 패스워드 어노테이션 
    //관련 내용 글에서 확인할 수 있다.
    @Password
    private String password;
    
    //공백을 방지 하는 검증
    @NotBlank(message = Constants.joinNameValidation)
    private String name;
}

@Valid Controller 등록

@RequestMapping("/api/delivery-service/general")
@RequiredArgsConstructor
public class GeneralMemberController {

    private final GeneralMemberService generalMemberService;

    @PostMapping("/member/join")
    public ResponseEntity joinMember(
        @Valid @RequestBody RequestGeneralMember requestGeneralMember) {
        generalMemberService.join(requestGeneralMember.getLoginName(),
            requestGeneralMember.getPassword(), requestGeneralMember.getName());
        ResponseGeneralMemberSuccess success = new ResponseGeneralMemberSuccess(201, null, null);
        return new ResponseEntity(success, HttpStatus.CREATED);
    }
}

작동 확인하기

공백인 name이 들어와, MethodArgumentNotValidException 예외가 발생하고

그 예외를 잡아서 원하는 Response 반환하는 것을 알 수 있다.

Validation의 종류

기본 제공 validation

@AssertFalse

주석이 달린 요소가 거짓인지 확인

@AssertTrue

주석이 달린 요소가 true인지 확인

@DecimalMax(value=, inclusive=)

inclusive=false 인 경우 주석 값이 지정된 최대값보다 작은지 확인 . 그렇지 않으면 값이 지정된 최대값보다 작거나 같은지 여부이다. 매개변수 값은 문자열 표현에 따른 최대값의 BigDecimal문자열 표현.

@Digits(integer=, fraction=)

주석이 달린 값이 integer자릿수와 fraction소수 자릿수 까지의 숫자인지 확인.

@Email

지정된 문자 시퀀스가 유효한 이메일 주소인지 확인. 선택적 매개변수 regexp이고 flags이메일이 일치해야 하는 추가 정규식(정규식 플래그 포함)을 지정

@Future

주석이 달린 날짜가 미래인지 확인

@FutureOrPresent

주석이 달린 날짜가 현재인지 미래인지 확인

@Past

주석이 달린 날짜가 과거인지 확인

@Max(value=)

주석이 달린 값이 지정된 최대값보다 작거나 같은지 확인

@Min(value=)

주석이 달린 값이 지정된 최소값보다 크거나 같은지 확인

@Size(min=, max=)

주석이 달린 요소의 크기가 min~ max(포함) 사이인지 확인

@NotBlank

주석이 달린 문자 시퀀스가 null이 아니고 잘린 길이가 0보다 큰지 확인합니다. 차이점 @NotEmpty은 이 제약 조건은 문자 시퀀스에만 적용할 수 있고 후행 공백은 무시된다는 것이다.

NotBlank는 String str = “ “ 공백일 때도 에러 발생, NotEmpty 공백일 때 에러 발생하지 않는다.

@NotNull

주석이 달린 값이 아닌지 확인null

@Null

주석이 달린 값이 다음과 같은지 확인한다.null

@PositiveOrZero

요소가 양수인지 0인지 확인한다

@Positive

요소가 완전히 양수인지 확인한다. 0 값은 유효하지 않은 것으로 간주된다

@Negative

요소가 완전히 음수인지 확인한다. 0 값은 유효하지 않은 것으로 간주된다.

@Pattern(regex=, flags=)

regex주어진 플래그를 고려 하여 주석이 달린 문자열이 정규식과 일치하는지 확인한다.match

하이버네이트 validation

Hibernate Validator는 아래에 나열된 몇 가지 유용한 사용자 정의 제약 조건을 제공한다.

@CreditCardNumber(ignoreNonDigitCharacters=)

주석이 달린 문자 시퀀스가 Luhn 체크섬 테스트를 통과하는지 확인한다. 이 유효성 검사는 신용 카드 유효성이 아니라 사용자 실수를 확인하는 것을 목표로 한다.

ignoreNonDigitCharacters숫자가 아닌 문자를 무시할 수 있다.. 기본값은 false이다.

@EAN

주석이 달린 문자 시퀀스가 유효한 EAN 바코드인지 확인합니다. type은 바코드의 유형을 결정합니다. 기본값은 EAN-13이다.

@DurationMax(days=, hours=, minutes=, seconds=, millis=, nanos=, inclusive=)

java.time.Duration주석이 달린 요소가 주석 매개변수에서 구성된 것보다 크지 않은지 확인한다 . inclusive플래그가 로 설정 되면 평등이 허용된다 true.

Duration이란, between() 정적 메서드에 시작 시간과 종료 시간을 넘기면 두 시간의 간격을 나타내는 Duration객체를 생성해준다.

@Length(min=, max=)

주석이 달린 문자 시퀀스가 사이에 min있고 max포함되어 있는지 확인힌다.

@Range(min=, max=)

주석 값이 지정된 최소값과 최대값 사이(포함)인지 확인한다.

@UniqueElements

주석이 달린 컬렉션에 고유한 요소만 포함되어 있는지 확인한다. 평등은 방법을 사용하여 결정된다 equals(). 기본 메시지에는 중복 요소 목록이 포함되어 있지 않지만 메시지를 재정의하고 {duplicates}message 매개변수를 사용하여 포함할 수 있다. 중복 요소 목록은 제약 조건 위반의 동적 페이로드에도 포함된다.

@URL(protocol=, host=, port=, regexp=, flags=)

주석이 달린 문자 시퀀스가 RFC2396에 따라 유효한 URL인지 확인한다. 선택적 매개변수 protocol또는 host가 port지정된 경우 해당 URL 조각은 지정된 값과 일치해야 한다. 선택적 매개변수 regexp이며 flagsURL이 일치해야 하는 추가 정규식(정규식 플래그 포함)을 지정할 수 있다. 기본적으로 이 제약 조건은 java.net.URL생성자를 사용하여 주어진 문자열이 유효한 URL을 나타내는지 여부를 확인한다.

@Getter
public class RequestDto {
    
    @URL(message = "유효한 URL 값이 아닙니다.")
    private String url;

    @URL(message = "깃허브 주소가 아닙니다.", protocol = "https", host = "github.com")
    private String githubUrl;
}

reference

https://www.daleseo.com/java8-duration-period/

https://hyeran-story.tistory.com/81

https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/?v=8.0#section-declaring-bean-constraints

https://mangkyu.tistory.com/174