반응형
Validation 시리즈
1. Spring Bean Validation - Basic
2. Spring Bean Validation - Custom Annotation
3. Spring Bean Validation - Test Code

앞 포스팅은 기본으로 작성되어있는 애너테이션을 사용하여, 유효성 검사를 했다. 하지만 프로젝트를 진행하다보면 여러가지 유효성을 검사해야할 일이 있다. 핸드폰 번호, 비밀번호 등 자주 쓰는 기능을 애너테이션으로 작성하여 재사용한다면 생산성은 높아질 것이다.

이러한 Custom 애너테이션을 위하여 인터페이스를 제공하고 있으며, 이를 구현하여 원하는 기능의 Validation 애너테이션을 만들 수 있다. 이번 포스팅에서 패스워드에 관련된 애너테이션을 작성해 보려고 한다

 

패스워드의 복잡성은 다음과 같다

- 영문, 숫자, 특수문자 중 2종류 이상을 조합하여 최소 10자리 이상 또는 3종류 이상을 조합하여 최소 8자리 이상의 길이로 구성

- 연속적인 숫자나 생일, 전화번호 등 추측하기 쉬운 개인정보 및 아이디와 비슷한 비밀번호는 사용하지 않는 것을 권고

 

위의 내용을 토대로 패스워드 Validation Annotation을 만들어 보자

Annotation Class

먼저 Annotation Class를 작성한다.

const val DEFAULT_PASSWORD_RULES : String = 
    """^(?=.*[a-zA-Z])((?=.*\d)(?=.*\W)).{8,16}${'$'}"""
const val DEFAULT_PASSWORD_INVALID_MESSAGE = 
    "비밀번호는 영문, 숫자, 특수문자를 포함 8자리 이상 입력하세요"

@Constraint(validatedBy = [PasswordValidation::class])
@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class PasswordValid(
    val patterns : Array<String> = [DEFAULT_PASSWORD_RULES],
    val message : String = DEFAULT_PASSWORD_INVALID_MESSAGE,
    val groups : Array<KClass<*>> = [],
    val payload : Array<KClass<out Payload>> = []
)

Bean Validation Annotation을 만들 때 message, groups, payload 를 작성해야 하는데 해당 내용은 Hibernate Validator를 참고하면된다

 

message : 제약조건을 위반한 경우 출력할 메세지

groups : 유효성 검사 그룹을 위한 속성, (Default 는 빈 배열이여야한다)

payload : 실제 Validator API 자체에서 사용되진 않지만 사용자 지정 Payload를 사용할 수 있게 해준다

ConstraintValidator 구현

위의 애너테이션의 실제 로직을 구현하기 위해서 ConstraintValidator 인터페이스를 구현해야한다

class PasswordValidation : ConstraintValidator<PasswordValid, String> {
    lateinit var patterns : Array<String>

    override fun initialize(constraintAnnotation: PasswordValid) {
        patterns = constraintAnnotation.patterns
    }

    override fun isValid(value: String, context: ConstraintValidatorContext): Boolean {
        return patterns.all { pattern ->
            Pattern.compile(pattern).matcher(value).matches()
        }
    }
}

ConstraintValidator은 2개의 메소드가 있다

initialize() : isValid를 호출하기 전에 초기화를 해야하는 항목이 있다면 여기서 하면 된다. 위의 코드에서는 유효성 검사를 위한 패턴들을 받는다. Default는 아무것도 작동하지 않는다

isValid() : 유효성검사 로직을 작성한다. 이 메소드는 필수로 구현해 주어야 한다

 

 위의 코드를 작성하고 패스워드 프로퍼티에 PasswordValid 애너테이션을 붙여주고 테스트를 해보자

data class Member(
    ...(생략)
    @field:PasswordValid
    val password: String
)
@WebMvcTest(controllers = [MemberController::class])
internal class MemberControllerTest {

    @Autowired
    private lateinit var mockMvc: MockMvc

    @Nested
    @DisplayName("패스워드 유효성 검사")
    inner class PasswordRules {
        @Test
        @DisplayName("성공")
        fun validPasswordRule() {
            var request = """
            {
                "name":"브라운", 
                "email":"brown@email.com", 
                "age":19,
                "count":4, 
                "password":"1qaz2wsx!@"
             }""".trimIndent()
            mockMvc.perform(
                MockMvcRequestBuilders.post("/members")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(request)
            )
                .andExpect(status().isOk)
        }

        @Test
        @DisplayName("자릿수 실패")
        fun invalidPasswordRule_Length() {
            var request = """
        {
            "name":"브라운", 
            "email":"brown@email.com", 
            "age":19,
            "count":4, 
            "password":"12345"
         }""".trimIndent()
            mockMvc.perform(
                MockMvcRequestBuilders.post("/members")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(request)
            )
                .andExpect(status().isBadRequest)
                .andExpect(jsonPath("$.errorCode").value("E001"))
                .andExpect(jsonPath("$.errorMessage")
                    .value("password : 비밀번호는 영문, 숫자, 특수문자를 포함 8자리 이상 입력하세요"))
        }

        @Test
        @DisplayName("특수문자 실패")
        fun invalidPasswordRule_char() {
            var request = """
        {
            "name":"브라운", 
            "email":"brown@email.com", 
            "age":19,
            "count":4, 
            "password":"1234567890"
         }""".trimIndent()
            mockMvc.perform(
                MockMvcRequestBuilders.post("/members")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(request)
            )
                .andExpect(status().isBadRequest)
                .andExpect(jsonPath("$.errorCode").value("E001"))
                .andExpect(jsonPath("$.errorMessage")
                    .value("password : 비밀번호는 영문, 숫자, 특수문자를 포함 8자리 이상 입력하세요"))
        }
    }
}

테스트가 성공했다

이와 같이 Custom 애너테이션을 작성해 두면 원하는 유효성 검사를 만들 수 있다.


출처

https://docs.jboss.org/hibernate/validator-backup/5.2/reference/en-US/html/validator-customconstraints.html

 

반응형

'개발 > Kotlin' 카테고리의 다른 글

Gradle - dependency-management Plugin  (0) 2022.06.19
QueryDSL 설정 - Kotlin  (0) 2022.06.19
Kotlin Basic - 함수와 변수  (0) 2022.06.18
Spring Bean Validation - Test Code  (0) 2022.06.14
Spring Bean Validation - Basic  (0) 2022.04.12

+ Recent posts