Validate 란?
검증을 의미하는 말로, form 또는 객체의 전달과정에서 유효하지 않은 값이 들어올 경우 필터링 하는 역할을 하는 검증 객체이다.
주로, spring에서 제공해주는 어노테이션을 사용하거나 직접 구현하여 validate를 구현하게 된다
현재 프로젝트에서는 직접 구현한 validate 및 spring 제공 어노테이션 ( + 커스텀 어노테이션 )을 구현하여 사용하였다
Validate 적용
1. Spring Annotation 사용
@NotNull, @Size, @Email, @Pattern 등을 사용하여 적절한 값을 통해 유효성을 검증
controller에서 객체나 form이 들어올 경우, @Validated 혹은 @Valid 과 BindingResult를 통하여 검증을 진행하게 된다
@Getter
@Setter
public class memberUpdateForm {
private Long id;
private String loginId;
@NotBlank(message="이름은 필수로 입력하여야 합니다.")
private String name;
@ValidPassword
private String passwd;
private String address;
}
@PostMapping("/{loginId}/edit")
public String postEditForm(@PathVariable("loginId") String loginId,
@Validated @ModelAttribute("member") memberUpdateForm form,
BindingResult bindingResult){
if (bindingResult.hasErrors()){
return "member/edit-form";
}
Member member = new Member();
member.setId(form.getId());
member.setLoginId(loginId);
member.setName(form.getName());
member.setPasswd(form.getPasswd());
member.setAddress(form.getAddress());
memberRepository.updateMember(loginId, member);
//수정 완료 알림
return "redirect:/members/" + loginId;
}
- @ValudPassword 의 경우 어노테이션을 커스텀할 필요가 있어서, 직접 어노테이션을 만들어서 유효성 검증을 진행하였다
- 구현체)
- ConstraintValidator<ValidPaswword, String> 을 이용하여 messeage 및 유효성 검증 어노테이션 상속
- isValid()구현
@Constraint(validatedBy = PasswordValidator.class) // Validator 연결
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPassword {
String message() default "비밀번호는 문자, 숫자, 특수문자를 포함하여 8자 이상이어야 합니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {
@Override
public void initialize(ValidPassword constraintAnnotation) {
}
@Override
public boolean isValid(String passwd, ConstraintValidatorContext context) {
if (passwd == null || passwd.length() < 8) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("비밀번호는 8자 이상이어야 합니다.")
.addConstraintViolation();
return false;
}
// 문자만 포함
if (passwd.matches("[a-zA-Z]+")) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("비밀번호는 숫자와 특수문자를 포함해야합니다.")
.addConstraintViolation();
return false;
}
// 숫자만 포함
if (passwd.matches("[0-9]+")) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("비밀번호는 문자와 특수문자를 포함해야합니다.")
.addConstraintViolation();
return false;
}
// 문자와 숫자만 포함
if (passwd.matches("[a-zA-Z0-9]+")) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("비밀번호는 특수문자를 포함해야합니다.")
.addConstraintViolation();
return false;
}
// 모든 조건을 만족
return true;
}
}
2. 직접 validate 적용
- InitBinder로 직접 구현한 Validate 클래스를 등록하지 않을경우, Validate 사용시 직접 언급해야 적용이 된다
ex)
@PostMapping("/form") public String registryMember(@Validated @ModelAttribute Member member, BindingResult bindingResult, Model model){
bindingResultValidate.validate(member, bindingResult);
if (bindingResult.hasErrors()){
return "member/member-form";
}
memberRepository.save(member);
return "redirect:/members/login";
}
- 구현체 ) Validator를 상속
- @Component로 스프링 빈 등록
- validate() 메서드 구현
//Controller{
//... this.bindingResultValidate = bindingResultValidate; //의존성 주입
@PostMapping("/form")
public String registryMember(@Validated @ModelAttribute Member member, BindingResult bindingResult, Model model){
if (bindingResult.hasErrors()){
return "member/member-form";
}
memberRepository.save(member);
return "redirect:/members/login";
}
@InitBinder
private void init(WebDataBinder webDataBinder){
webDataBinder.addValidators(bindingResultValidate);
}
}
//Validator를 상속 받아서 구현
@Component
public class BindigResultValidate implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Member.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Member member = (Member) target;
BindingResult bindingResult = (BindingResult) errors;
//로그인ID
if ( !StringUtils.hasText(member.getLoginId())){
bindingResult.addError(new FieldError("member", "loginId","로그인 아이디는 필수입력입니다."));
}
//이름
if (!StringUtils.hasText(member.getName())){
bindingResult.addError(new FieldError("member", "name", "이름은 필수 입력값입니다."));
}
//비밀번호
if ( member.getPasswd() != null && member.getPasswd().length() >= 8 ){
if (member.getPasswd().matches("[a-zA-Z]+")){
bindingResult.addError(new FieldError("member","passwd", "비밀번호는 숫자와 특수문자를 포함해야합니다."));
}else if (member.getPasswd().matches("[0-9]+")) {
bindingResult.addError(new FieldError("member", "passwd", "비밀번호는 문자와 특수문자를 포함해야합니다"));
}else if (member.getPasswd().matches("[a-zA-Z0-9]+")){
bindingResult.addError(new FieldError("member","passwd", "비밀번호는 특수문자를 포함해야합니다 "));
}
}else{
bindingResult.addError(new FieldError("member","passwd", "비밀번호는 문자,숫자,특수문자를 포함하여 8자이상이어야합니다."));
}
}
}