🍀 Spring Boot

[Spring Boot] Day89 - Validator (유효성검사)

harveydent 2023. 9. 15. 08:59
728x90

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

 

GitHub - Qkrwnsgus0522/SpringBoot

Contribute to Qkrwnsgus0522/SpringBoot development by creating an account on GitHub.

github.com

 

728x90