안녕하세요. 오늘은 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();
현재는 startDate와 endDate만 검증을 해봤는데, 실제로 필요한 검증을 직접 추가하시고 소스코드도 더 리팩토링하셔서 사용을 하시면 될 것 같습니다!
이상으로 오늘은 스프링 부트에서 요청값을 검증하는 방법에 대하여 알아봤습니다!
아직 모르는게 많아 게시글에 잘못된 정보가 있을 수 있습니다. 혹시 잘못된 정보가 있다면, 댓글 혹은 메일로 알려주시면 최대한 빨리 수정하겠습니다!