본문 바로가기

IT/Spring

[Spring Boot] ConstraintValidator Custom 하기

Spring에서 JSR 303 어노테이션을 이용해 데이터 유효성검사를 진행할 수 있습니다.
보통 @NotBlank, @Size, @NotNull ...등 이미 만들어진 검증 어노테이션을 이용할 수 있지만, 자신의 목적에 맞는 검증 어노테이션을 커스텀하여 제작할 수 있습니다.

간단하게 password를 검증하는 어노테이션을 만들어보겠습니다.

1. Password Annotation 생성

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import kr.co.tlab.ppl.validator.impl.PasswordValidator;

@Documented
@Constraint(validatedBy = PasswordValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Password {

    String message() default "";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    public int min() default 0;

    public int max() default 2147483647;

    public boolean nullable() default false;
}

2. Password 검증 로직이 있는 ConstraintValidator 구현 

  • 비밀번호는 정해진 길이만큼 입력해야한다. (min ~ max)
  • 비밀번호는 nullable 속성을 통해 Null값을 받을 수 있다. (true/false)
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import kr.co.tlab.ppl.validator.Password;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

public class PasswordValidator implements ConstraintValidator<Password, String> {

    private int min;
    private int max;
    private boolean nullable;

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void initialize(Password passwordValidator) {
        //어노테이션 등록 시, 입력했던 Parameter를 초기화한다.
        min = passwordValidator.min();
        max = passwordValidator.max();
        nullable = passwordValidator.nullable();
    }

    @Override
    public boolean isValid(String password, ConstraintValidatorContext context) {
        //if password is not blank
        if (StringUtils.hasText(password)) {
            if (password.length() < min || password.length() > max) {
                addConstraintViolation(
                        context,
                        String.format("비밀번호는 %d자 ~ %d자 사이로 입력해주세요.", min, max)
                );
                return false;
            }
        } 
        else if (!nullable){
            addConstraintViolation(
                    context,
                    "비밀번호를 입력해주세요."
            );
            return false;
        }

        return true;
    }

    private void addConstraintViolation(ConstraintValidatorContext context, String msg) {
        //기본 메시지 비활성화
        context.disableDefaultConstraintViolation();
        //새로운 메시지 추가
        context.buildConstraintViolationWithTemplate(msg).addConstraintViolation();
    }

}

 

3. Member

public class Member{

    private Integer id;
    
    @NotBlank
    private String account;

    //Password 검증 어노테이션 등록
    @Password(min = 3, max = 10, nullable = true)
    private String password;

    @NotBlank
    private String name;

    private String email;

	//setter,getter 생략
}

 

4. ValidationTestController 

@Controller
@RequestMapping(value = "")
public class ValidationTestController {

    @PostMapping(value = "/member/save")
    @ResponseBody
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void saveMember(@Valid Member member) {
    	... save logic
    }
    
}

 

5. 비밀번호 "1" 입력 시 Response 결과

{
    "timestamp": 1594282566312,
    "status": 400,
    "error": "Bad Request",
    "errors": [{
            "codes": ["Password.member.password", "Password.password", "Password.java.lang.String", "Password"],
            "arguments": [{
                    "codes": ["member.password", "password"],
                    "arguments": null,
                    "defaultMessage": "password",
                    "code": "password"
                }, 10, 3, true],
            "defaultMessage": "비밀번호는 3자 ~ 10자 사이로 입력해주세요.",
            "objectName": "member",
            "field": "password",
            "rejectedValue": "1",
            "bindingFailure": false,
            "code": "Password"
        }],
    "message": "Validation failed for object='member'. Error count: 1",
    "path": "/member/save"
}