프론트에서 유효성 검사를 수행하지만, 백단에서도 유효성 검사를 수행해야 안전한 애플리케이션을 만들 수 있습니다.
Spring Boot의 Validation은 가장 많이 쓰이는 유효성 검사 라이브러리 입니다. 적용하는 방법은 구글링하면 수많은 포스팅이 존재하니 생략하고 바로 이슈 및 해결을 설명드리겠습니다.
public class UrlDto {
public static class Request{
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Save{
@NotBlank(message = "Link 필드는 필수입니다.")
@Pattern(regexp="^((http(s?))\\:\\/\\/)([0-9a-zA-Z\\-]+\\.)+[a-zA-Z]{2,6}(\\:[0-9]+)?(\\/\\S*)?$",
message = "Link 형식이 유효하지 않습니다.")
private String url;
}
}
url을 검사하는 코드입니다. url의 Validation에는 '@NotBlank'(빈값 조사)와 '@Pattern'(http(s) url 정규표현식)이 적용되어 있습니다.
이 상태로 Postman에서 테스트 해보면 아래의 결과가 뜹니다.
원했던 것은, 우선순위를 적용해서 하나의 검증이 실패하면 그 메시지만 리턴하고 검증을 통과하면 그 다음 항목을 검증하는 식으로 진행하고 싶었습니다. 그래서 1순위로 NotBlank를 검사하고, 2순위로 정규표현식을 검사해서 그에 맞는 메시지만을 리턴하게 만들어야 합니다. 수행한 방법은 아래순서와 같습니다.
1. 먼저 인터페이스를 생성해줍니다. 따로 만들면 파일이 늘어나니, 클래스 하나를 만들고 그안에 정의하는 방식을 선택했습니다.
public class ValidationGroups {
public interface NotEmptyGroup {};
public interface PatternCheckGroup {};
}
2. @GroupSequence를 사용하여 그룹별 인터페이스를 정의해줍니다.
왼쪽부터 유효성 검사를 체크해서 오류가 없으면 다음 유효성 검사를 하는 순서로 진행이 됩니다
Default.class -> NotEmptyGroup.class -> PatternCheckGroup.class
import foo.study.url.dto.ValidationGroups.NotEmptyGroup;
import foo.study.url.dto.ValidationGroups.PatternCheckGroup;
import javax.validation.GroupSequence;
import javax.validation.groups.Default;
@GroupSequence({Default.class, NotEmptyGroup.class, PatternCheckGroup.class, })
public interface ValidationSequence {
}
3. Validaton 어노테이션에서 각각 groups = "인터페이스명"을 추가해줍니다.
public class UrlDto {
public static class Request{
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Save{
@NotBlank(message = "Link 필드는 필수입니다.", groups = ValidationGroups.NotEmptyGroup.class)
@Pattern(regexp="^((http(s?))\\:\\/\\/)([0-9a-zA-Z\\-]+\\.)+[a-zA-Z]{2,6}(\\:[0-9]+)?(\\/\\S*)?$",
message = "Link 형식이 유효하지 않습니다.", groups = ValidationGroups.PatternCheckGroup.class)
private String url;
}
4. 기존에는 @Valid를 사용했지만, 그룹 시퀀스를 지정하기 위해 @Validated 어노테이션을 사용합니다.
@PostMapping("/save")
public Response.FindOne<String> save( @Validated(ValidationSequence.class) @RequestBody UrlDto.Request.Save dto){
return FindOne.<String>builder()
.data(
urlService.save(dto.getUrl()
)
).build();
}
5. 테스트
테스트 코드는 다음과 같습니다.
class UrlDtoTest {
private static ValidatorFactory factory;
private static Validator validator;
@BeforeAll
public static void init(){
factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
@DisplayName("url에 빈값 전송시 에러발생")
@Test
void save_notblank_validation(){
Request.Save saveDto = Request.Save.builder().url("").build();
// 유효하다면 violations는 빈 값, 유효하지 않다면 값을 가지고 있음.
Set<ConstraintViolation<Request.Save>> violations = validator.validate(saveDto, ValidationGroups.NotEmptyGroup.class);
violations
.forEach(error -> {
assertThat(error.getMessage()).isEqualTo("Link 필드는 필수입니다.");
});
}
@DisplayName("url에 http 표현식이 아닌 값 전송시 에러발생")
@Test
void save_pattern_validation(){
Request.Save saveDto = Request.Save.builder().url("not http expressions").build();
Set<ConstraintViolation<Request.Save>> violations = validator.validate(saveDto, ValidationGroups.PatternCheckGroup.class);
violations
.forEach(error -> {
assertThat(error.getMessage()).isEqualTo("Link 형식이 유효하지 않습니다.");
});
}
}
적용한 프로젝트는 깃허브를 확인해주세요.
참고
stackoverflow.com/questions/11804879/error-messages-are-not-in-the-correct-order
javapointers.com/spring/spring-mvc/spring-mvc-validation-order-example/
www.notion.so/lightblog/Bean-Validation-2f70a3f0aae94621886487477097abfa
테스트 코드
discourse.hibernate.org/t/groupsequence-combined-with-valid-doest-work-in-all-cases/840
velog.io/@tigger/DTO-%EA%B2%80%EC%A6%9D-%EB%B0%8F-%ED%85%8C%EC%8A%A4%ED%8A%B8
m.blog.naver.com/varkiry05/222058714706
'Tech > Spring' 카테고리의 다른 글
Spring Boot에서 Actuator 및 Spring Actuator Admin 설정 방법 (0) | 2021.03.21 |
---|---|
Google 소셜 로그인 Google API 클라이언트 라이브러리 + GoogleIdTokenVerifier 사용하는 방법 (4) | 2021.03.15 |
SpringBoot에서 HttpOnly 쿠키방식을 이용한 refreshToken 발급 (0) | 2021.02.27 |
@Controller와 @RestController의 차이점 (2) | 2021.02.12 |
[취준생을 위한 스프링부트 백엔드 프로그래밍] 3주차 과제 (0) | 2021.01.26 |