Django로 비밀번호 유효성 검사 구현하기

요구 정책

  • 8자리 이상
  • 영문, 숫자, 특수문자 포함

구현 방식

  • DRF가 아닌 Pure Django에서 구현했습니다.
  • validators.py에 원하는 검사를 구현하고, forms.py에 validator를 추가했습니다.

구현

settings.py

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
    {
        'NAME': 'users.validators.CustomPasswordValidator', # 추가
    },
]

VALIDATORS를 추가하였습다. 구현한 validators.py에서도 기존의 validator도 사용하기 때문에 기존 validator 및에 추가하였습니다.

validators.py

from django.core.exceptions import ValidationError
from django.contrib.auth.password_validation import validate_password
import re


class CustomPasswordValidator:
    def __call__(self, value): # 1)
        try:
            validate_password(value)
        except ValidationError as e:
            raise ValidationError(str(e))

    def validate(self, password, user=None):
        """비밀번호 유효성 검사"""

        if len(password) < 8:
            raise ValidationError("비밀번호는 8자리 이상이어야 합니다.")
        if not re.search(r"[a-zA-Z]", password):
            raise ValidationError("비밀번호는 하나 이상의 영문이 포함되어야 합니다.")
        if not re.search(r"\d", password):
            raise ValidationError("비밀번호는 하나 이상의 숫자가 포함되어야 합니다.")
        if not re.search(r"[!@#$%^&*()]", password):
            raise ValidationError(
                "비밀번호는 적어도 하나 이상의 특수문자(!@#$%^&*())가 포함되어야 합니다."
            )

    def get_help_text(self): # 2)
        return "비밀번호는 8자리 이상이며 영문, 숫자, 특수문자((!@#$%^&*()))를 포함해야 합니다."

이렇게 구현했습니다.
위에처럼 정규 표현식이 아닌 아래처럼 일반 파이썬 문으로도 구현이 가능합니다.

        if not any(char.isalpha() for char in value):
            raise forms.ValidationError("비밀번호는 하나 이상의 영문이 포함되어야 합니다.")
        if not any(char.isalpha() for char in value):
            raise forms.ValidationError("비밀번호는 하나 이상의 숫자가 포함되어야 합니다.")
        if not any(char in "!@#$%^&*()" for char in value):
            raise forms.ValidationError("비밀번호는 적어도 하나 이상의 특수문자((!@#$%^&*()))가 포함되어야 합니다.")

1)

이 클래스 CustomPasswordValidator는 callable 객체를 정의합니다. 클래스의 인스턴스를 함수로 호출할 수 있는 __call__ 메서드를 구현합니다. 이 경우 'CustomPasswordValidator' 인스턴스는 암호 값을 인수로 사용하여 호출할 수 있습니다.

__call__ 메서드 내에서 validate_password 함수가 호출되어 django가 기본으로 제공하는 암호 유효성 검사를 실행합니다. 유효성 검사 오류가 발생하면 오류 메시지와 함께 ValidationError가 발생합니다.

다시 말하면, __call__이 메소드는 SignUpForm 클래스의 validators=[CustomPasswordValidator()] 행과 같이 클래스의 인스턴스가 함수로 호출될 때 호출됩니다. CustomPasswordValidator()가 호출되면 __call__ 메서드가 트리거되고 validate_password() 함수를 호출합니다.

또한 CustomPasswordValidator 클래스는 validate 메서드도 정의합니다. 이 메서드는 인스턴스가 함수로 호출될 때(__call__ 메서드와 같이) 자동으로 실행되지 않지만 validate_password()가 수행하는 것 이상의 추가 암호 유효성 검사 규칙을 제공합니다. 이러한 추가 규칙은 비밀번호의 길이와 영문, 숫자 및 특수 문자를 포함하는지 여부를 확인합니다.

따라서 SignUpForm 클래스에서 CustomPasswordValidator()를 유효성 검사기로 사용하면 validate_password() 함수와 validate() 메서드가 모두 실행되어 비밀번호를 검증합니다.

2)

get_help_text 메서드는 암호 요구 사항에 대한 정보를 제공하는 도움말 텍스트를 반환합니다. 이는 예상되는 암호 형식에 대한 추가 정보를 사용자에게 표시하는 데 유용할 수 있습니다.

forms.py

from django import forms
from .validators import CustomPasswordValidator


class SignUpForm(forms.Form):
    ... 생략 ...
    password = forms.CharField(
        widget=forms.PasswordInput,
        validators=[CustomPasswordValidator()],
    )
    ... 생략 ...

이렇게 import 해서 사용합니다.