[Java] 커스텀 애노테이션으로 Password규칙 적용하기

Livenow·2021년 1월 15일
1

스프링 프레임워크가 기본적으로 제공하는 Validator이외의 Validator를 구현해야할 순간 이있습니다.

스프링은 컨트롤러에서 클라이언트에서 넘겨받은 값에 대한 검증을 JSR-303 기반으로 쉽고 강력하게 할 수 있습니다. 또 한 커스텀 한 어노테이션을 쉽게 구현할 수 있고 확장도 용이합니다.


애노테이션을 직접 만들기 위해서는 2가지 클래스가 필요합니다.

@interface 클래스, ConstraintValidator<@interface 클래스, String>의 구현 클래스


아래에서 작성하는 어노테이션은 해당 Password가 유효한지 검사를 하는 애노테이션입니다.

애노테이션 정의

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Constraint(validatedBy = PasswordValidator.class)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface Password {

    String message() default "Password is not allow";

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

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

    자바에는 Javadoc이라고 코드를 문서화 하는 기능이 있습니다. 이 애노테이션을 가지고 있는 애노테이션을 사용하시면 해당 애노테이션 정보를 해당 코드의 문서에 같이 보여줍니다

  • @Constraint

    애노테이션을 Bean Validation Constraint로 만들어주는 애노테이션입니다.

    이 애노테이션을 가진 클래스는 반드시 아래와 같은 속성값을 가져야합니다.

    속성설명
    String message() default [...];해당 attribute는 default message key 값을 가지고 있어야함
    Class<?>[] groups() default {};사용자들이 targeted group을 customize하기 위해 사용
    Class<? extends Payload>[] payload() default {};확장성을 위해 사용
  • @Target

    해당 애노테이션이 적용될 수 있는 contexts값을 의미. 즉, 어디에 사용될 수 있는지 정의

    속성설명
    ElementType.PACKAGEpackage declaration
    ElementType.TYPEClass, interface(including annotation type), or enum   declaration
    ElementType.CONSTRUCTORConstructor declaration
    ElementType.FIELDField declaration (includes enum constants)
    ElementType.METHODMethod declaration
    ElementType.ANNOTATION_TYPEannotation type declaration
    ElementType.LOCAL_VARIABLELocal variable declaration
    ElementType.PARAMETERType parameter declaration
    ElementType.TYPE_USEUse of a type
  • @Retention

    해당 애노테이션을 언제까지 유지할 것인가에 대한 설정

    속성설명
    SOURCEAnnotaions are to be discarded by the compoiler
    CLASSAnnotaions are to be recorded in the class file by the compiler
    but need not be retained by th VM at run time. This is the default behaivor
    RUNTIMEAnnotaions are to be recorded in the class file by the compiler. retained by th VM at run time.
    so they may be read reflectively

Validator 로직(ConstraintValidator 구현 클래스) 작성

import org.domain.user.Password;
import org.springframework.stereotype.Component;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.text.MessageFormat;

@Component
public class PasswordValidator implements ConstraintValidator<Password, String> {

    private static final int MIN_SIZE = 8;
    private static final int MAX_SIZE = 50;
    private static final String regexPassword = "^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[$@$!%*#?&])[A-Za-z[0-9]$@$!%*#?&]{" + MIN_SIZE
            + "," + MAX_SIZE + "}$";

    @Override
    public void initialize(Password constraintAnnotation) {
    }

    @Override
    public boolean isValid(String password, ConstraintValidatorContext context) {
        boolean isValidPassword = password.matches(regexPassword);
        if (!isValidPassword) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(
                    MessageFormat.format("{0}자 이상의 {1}자 이하의 숫자, 영문자, 특수문자를 포함한 비밀번호를 입력해주세요", MIN_SIZE, MAX_SIZE))
                    .addConstraintViolation();
        }
        return isValidPassword;
    }

    public boolean isValid(String password) {
        return password.matches(regexPassword);
    }
}

아규먼트로 받은 password값이 지정한 규칙에 맞는지 확인하고, 규칙에 맞지않으면 예외 메세지를 추가후, false를 리턴 합니다.

이제 @Password로 지정된 변수는 해당 애노테이션 로직이 적용되어 regexPassword(지정한 password규칙)에 맞지않으면 false를 리턴할 것입니다.

profile
경험의 연장선

0개의 댓글