본문 바로가기
Study/SpringBoot

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

by 검프 2021. 1. 28.

스프링 프레임워크가 기본적으로 제공하는 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이 애노테이션을 가진 클래스는 반드시 아래와 같은 속성값을 가져야합니다.
    속성 설명
    String message() default [...]; 해당 attribute는 default message key 값을 가지고 있어야함
    Class<?>[] groups() default {}; 사용자들이 targeted group을 customize하기 위해 사용
    Class<? extends Payload>[] payload() default {}; 확장성을 위해 사용
  • 애노테이션을 Bean Validation Constraint로 만들어주는 애노테이션입니다.
  • @Target
    속성 설명
    ElementType.PACKAGE package declaration
    ElementType.TYPE Class, interface(including annotation type), or enum declaration
    ElementType.CONSTRUCTOR Constructor declaration
    ElementType.FIELD Field declaration (includes enum constants)
    ElementType.METHOD Method declaration
    ElementType.ANNOTATION_TYPE annotation type declaration
    ElementType.LOCAL_VARIABLE Local variable declaration
    ElementType.PARAMETER Type parameter declaration
    ElementType.TYPE_USE Use of a type
  • 해당 애노테이션이 적용될 수 있는 contexts값을 의미. 즉, 어디에 사용될 수 있는지 정의
  • @Retention
    속성 설명
    SOURCE Annotaions are to be discarded by the compoiler
    CLASS Annotaions 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
    RUNTIME Annotaions 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.greenbyme.angelhack.domain.user.Password;
import org.springframework.stereotype.Component;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.text.MessageFormat;
import java.util.regex.Pattern;

@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 + "}$";
    private static final Pattern PATTERN = Pattern.compile(regexPassword);

    @Override
    public void initialize(Password constraintAnnotation) {
    }

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

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

    public boolean matches(String password) {
        return PATTERN.matcher(password).matches();
    }
}

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

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

댓글