안녕하세요. 오늘은 Spring boot 에서 request에 들어오는 요청값을 검증하는 법을 알아보겠습니다.
현재 제가 공부로 진행중인 프로젝트에서 소스를 조금 가져오겠습니다.

먼저 AccountController에 있는 createAccount메소드를 가져오겠습니다.

@PostMapping
    public ResponseEntity createAccount(@RequestBody @Valid AccountDto dto, Errors errors) {
        if (errors.hasErrors()) {
            return ResponseEntity.badRequest().build();
        }

        Account account = accountService.save(dto);

        return ResponseEntity.ok(
                AccountDto.builder()
                        .displayName(account.getDisplayName())
                        .email(account.getEmail())
                        .build()
        );
    }

현재 소스코드에서 보면 @RequestBody@Valid 어노테이션이 AccountDto 요청값에 붙어있는데,

@RequestBody 어노테이션은 @RequestMapping에 의해 POST 방식으로 전송된 HTTP 요청 데이터를 제가 지정해준 AccouontDto에 맞춰서 변환해주는 역할을 합니다.

@Valid 어노테이션이 바로 요청데이터를 검증하는 어노테이션입니다.

여기서 그냥 @Valid 어노테이션을 붙인다고해서 바로 검증을 할 수 있는 것은 아니고 실제 AccountDto에도 필드별로 검증하기 위해 다양한 어노테이션을 붙여줍니다.

@Getter
@Setter
@Builder
@AllArgsConstructor
public class AccountDto {

    @NotEmpty @NotBlank
    private String displayName;
    @NotEmpty @NotBlank @Email
    private String email;
    @NotEmpty
    @NotBlank
    @JsonIgnore
    private String password;

    public Account toEntity(){
        return Account.builder()
                .displayName(this.displayName)
                .password(this.password)
                .email(this.email)
                .build();
    }
}

각 필드마다 위에 @NotEmpty, @Email, @NotBlank
등 다양한 어노테이션이 보입니다.
실제로 @javax.validation.constraints.까지 입력하시고, Ctrl + Space키를 눌러보시면 하단에

이렇게 많은 어노테이션이 존재합니다.

각 어노테이션은 이름에서도 대충 유추를 할 수 있지만, 더 자세히 알고 싶으시면 여기를 더 참고하시면 좋을 것 같습니다.

이제 이런식으로 각 요청값을 @Valid 어노테이션을 이용하여서 걸러낼 수 있다는 것을 알아봤는데 , 만약 요청값이 @Email인데 이메일 형식이 아닌값이 들어오는 등, 요청값이 검증에 실패할 경우엔 어떻게 될까요?

위의 컨트롤러 소스코드를 다시보시면 createAccount의 두번째 인자로 Errors를 받고 있는 것을 보실 수 있습니다.

Validation 과정에서 실패하거나 에러가 발생하면 이 Errors에 에러들이 담기기 때문에 저같은 경우는 errors.hasErrors()를 이용해서 에러 발생시 예외처리를 해주고 있습니다.

하지만 위에 javax.validation.constraints.*에 제가 필요한 검증이 어노테이션으로 존재하지 않는 경우에는 어떤 방식으로 검증을 해야할까요?

최근 백기선님의 REST API강의를 하면서 배웠던 방식을 한번 보겠습니다. 예를 들기 위해 임의로 Event라는 객체를 아주 간단히 만들어 보겠습니다.

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter @Setter
@Entity
public class Event {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private String description;
    private LocalDate startDate;
    private LocalDate endDate;
}

이를 요청값으로 받을 때는 Entity클래스가 DTO클래스를 따로 생성하는게 좋기 때문에 이번에는 EventDto클래스를 만들어 보겠습니다.

@Getter
@Setter
@Builder
@NoArgsConstructor @AllArgsConstructor
public class EventDto {
    @NotEmpty
    private String name;
    @NotEmpty
    private String description;
    @NotNull
    private LocalDate startDate;
    @NotNull
    private LocalDate endDate;

현재는 위에 방식과 마찬가지로 @Valid를 사용한 방식만을 사용하고 있습니다.
하지만 현재 만일 startDate 보다 endDate가 더 이른 날짜가 들어온다면 이를 검증할 수 없다는 문제가 있습니다.( startDate = 20190218, endDate = 20190201)

이를 해결하기 위하여 저희는 EventValidator라는 클래스를 생성할 것입니다.

@Component
public class EventValidator {
    public void validate(EventDto eventDto, Errors errors){
         if(endDate.isBefore(startDate)){
            errors.rejectValue("endDate", "wrongValue", "endDate is wrong");
        }
    }

이런식으로 EventValidator@Component로 등록해준 뒤,
EventController에서 불러와 사용할 수 있습니다.

@Autowired
private EventValidator eventValidator;

@PostMapping
    public ResponseEntity createEvent(@RequestBody @Valid EventDto eventDto, Errors errors) {

        if(errors.hasErrors()) {
            return ResponseEntity.badRequest().body(errors);
        }

        eventValidator.validate(eventDto, errors);

        if(errors.hasErrors()){
            return ResponseEntity.badRequest().body(errors);
        }

            ...이벤트 생성코드 작성...

        return ResponseEntity.ok().build();

현재는 startDateendDate만 검증을 해봤는데, 실제로 필요한 검증을 직접 추가하시고 소스코드도 더 리팩토링하셔서 사용을 하시면 될 것 같습니다!

이상으로 오늘은 스프링 부트에서 요청값을 검증하는 방법에 대하여 알아봤습니다!


아직 모르는게 많아 게시글에 잘못된 정보가 있을 수 있습니다. 혹시 잘못된 정보가 있다면, 댓글 혹은 메일로 알려주시면 최대한 빨리 수정하겠습니다!