Validator (유효성 검사)
유효성 검사는 웹 애플리에킹션에서 사용자의 입력 데이터를 검증하여 유효한 데이터인지 확인하는 프로세스입니다. 유효성 검사는 클라이언트 측(웹 브라우저)에서 수행되는 클라이언트 측 유효성 검사와 서버 측(백엔드)에서 수행되는 서버 측 유효성 검사로 나눌 수 있습니다. 각각의 방식에는 고유한 특징과 장단점이 있습니다.
클라이언트 측 유효성 검사
클라이언트 측 유효성 검사는 웹 브라우저에서 사용자의 입력값을 검사하는 방식입니다. 주로 HTML과 JavaScript를 사용하여 구현됩니다.
- 장점
- 빠른 피드백 : 사용자는 즉시 입력 오류를 볼 수 있으므로 빠른 피드백을 제공합니다.
- 네트워크 트래픽 절약 : 서버로 유효성 검사 요청을 보내지 않으므로 서버 부하가 줄어듭니다.
- 단점
- 보안 문제 : 클라이언트 측 검사는 보안상 취약할 수 있으며, 악의적인 사용자가 스크립트를 조작하여 우회할 수 있습니다.
- 중복 검사 : 서버 측에서도 검사해야 하므로 중복 코드가 발생할 수 있습니다.
- 클라이언트에 의존 : 클라이언트가 JavaScript를 비활성화하거나 지원하지 않는 경우에는 유효성 검사가 동작하지 않을 수 있습니다.
서버 측 유효성 검사
서버 측 유효성 검사는 백엔드에서 사용자 입력을 검사하는 방식입니다. 입력 데이터를 서버로 보내고 서버에서 유효성을 검사합니다. 주로 백엔드 프레임워크나 라이브러리를 사용하여 구현됩니다.
- 장점
- 보안 강화 : 서버 측 검사는 클라이언트 측 검사에 비해 보안을 강화합니다. 악의적인 사용자가 클라이언트 측 검사를 우회하는 것을 방지할 수 있습니다.
- 중복 검사 : 서버 측에서 유효성 검사를 수행하므로 중복 코드를 최소화할 수 있습니다.
- 단점
- 느린 피드백 : 클라이언트 측 검사와 달리 서버로 데이터를 보내고 검사 결과를 기다려야 하므로 피드백이 느릴 수 있습니다.
- 서버 부하 : 서버에서 검사를 수행하므로 클라이언트 측 검사보다 서버 부하가 높을 수 있습니다.
Validator 인터페이스를 통한 유효성 검사
package com.jun.app;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
public class VOValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
// 유효성 검사할 객체의 클래스 정보를 반환
return VO.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) { // Errors errors도 커맨드 객체, 참조 변수
// target : 유효성 검사할 객체
// errors : 검증이 통과되지 못한 경우, 왜 통과가 안되었는지를 반환
VO vo = (VO)target;
String id = vo.getId();
String password = vo.getPassword();
if (id == null || id.isEmpty() || id.isBlank() || id.equals("") || id.trim().isEmpty() || id.trim().isBlank()) {
System.out.println("로그 : id값이 올바르지 않습니다.");
errors.rejectValue("id", "id값 없음");
}
if (id.length() > 5) {
errors.rejectValue("id", "id는 5자이하일 수 없습니다!");
}
if (password == null || password.isEmpty() || password.isBlank() || password.equals("") || password.trim().isEmpty() || password.trim().isBlank()) {
System.out.println("로그 : password값이 올바르지 않습니다.");
errors.rejectValue("password", "password값 없음");
}
// ValidationUtils.rejectIfEmptyOrWhitespace(errors, "id", "id값 없음");
// ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "password값 없음");
}
}
supports 메서드와 validate 메서드를 오버라이드 해야하기 때문에 Validator 인터페이스를 상속받아 구현합니다.
기존 방식
@Controller
public class CTRL {
@RequestMapping("/test")
public String root(VO vo, Model model) {
if (vo.getId() == null || vo.getId().isBlank() || vo.getId().equals("") || vo.getId().isEmpty()) {
}
if (vo.getPassword().length() <= 5) {
}
model.addAttribute("apple", vo.getId());
return "test";
}
}
기존의 방식은 VO 객체의 필드에 직접 유효성 검사를 수행하고 있습니다.
VOValidator 방식
@Controller
public class CTRL {
@RequestMapping("/test")
public String root(VO vo, BindingResult br, Model model) {
VOValidator voV = new VOValidator();
voV.validate(vo, br); // br는 커맨드 객체(참조 변수)
if (br.hasErrors()) {
System.out.println("로그 : 에러발생");
}
model.addAttribute("apple", vo.getId());
return "test";
}
}
VOValidator 클래스를 이용한 유효성 검사는 별도의 유효성 검사 클래스에서 수행하므로 검사 로직이 분리되며 재사용이 가능합니다. 또한 BindingResult를 사용하여 검사 결과를 처리하므로 에러 메세지를 효과적으로 다룰 수 있습니다.
@InitBinder 활용
@Controller
public class CTRL {
@RequestMapping("/")
public String root() {
return "test";
}
@RequestMapping("/test")
public String root(@Valid VO vo, BindingResult br, Model model) {
if (br.hasErrors()) {
System.out.println("로그 : 발생한 에러목록");
System.out.println(br.getAllErrors());
if (br.getFieldError("id") != null) { // id에서 Error가 발생했다는 의미
System.out.println(br.getFieldError("id").getCode());
}
if (br.getFieldError("password") != null) {
System.out.println(br.getFieldError("password").getCode());
}
}
model.addAttribute("apple", vo.getId());
return "test";
}
@InitBinder
protected void initBinder(WebDataBinder wdb) {
wdb.setValidator(new VOValidator());
}
}
이 방식은 커맨드 객체에 @Valid 어노테이션을 추가하고 BindingResult를 사용하여 검사 결과를 처리하는 방식입니다. 또한 @InitBinder를 사용하여 유효성 검사를 위한 VOValidator를 설정하였습니다. 해당 방식은 유지보수가 용이하지만 클래스를 따로 만들어 주어야 한다는 점이 있습니다.
Validation 의존성 사용
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
build.gradle
build.gradle 파일에 spring-boot-starter-validation 의존성을 추가합니다.
@Data
public class VO {
@NotNull(message="id값 Null")
@NotEmpty(message="id값 Empty")
@Size(min=5, max=100, message="id값 6이상 100이하 가능")
private String id;
@NotNull(message="password값 Null")
@NotEmpty(message="password값 Empty")
private String password;
}
VO.java
@NotNull 어노테이션을 통해 필드가 null이 아니어야 함을 나타내고, @NotEmpty 어노테이션을 통해 필드가 비어 있지 않아야 함을 나타내며, @Size 어노테이션을 통해 최소, 최대 글자수를 나타냅니다. 또한 각 어노테이션에는 message 속성이 포함되어 있으며, 해당 규칙을 위반할 경우 출력할 사용자 정의 오류 메세지를 지정할 수 있습니다.
@Controller
public class CTRL2 {
@RequestMapping("/test2")
public String test2(@Valid VO vo, BindingResult br, Model model) {
if (br.hasErrors()) {
System.out.println("로그 : 발생한 에러목록");
System.out.println(br.getAllErrors());
if (br.getFieldError("id") != null) { // id에서 Error가 발생했다는 의미
System.out.println(br.getFieldError("id").getDefaultMessage());
}
if (br.getFieldError("password") != null) {
System.out.println(br.getFieldError("password").getDefaultMessage());
}
}
model.addAttribute("apple", vo.getId());
return "test";
}
}
@Valid 어노테이션을 통해 VO 객체의 유효성을 검사한 후 검사 실패 시 에러 정보에서 기본 메세지를 가져와 출력합니다.
실행 결과
GitHub
https://github.com/Qkrwnsgus0522/SpringBoot