[Flask] Flask-WTF

Alexandria·2023년 11월 10일
0

Python3 Flask

목록 보기
5/14
post-thumbnail

1. Flask-WTF

Flask와 함께 사용되는 폼 처리를 위한 패키지입니다.

CSRF(Cross-Site Request Forgery) 공격으로부터 보호하기 위한 CSRF 토큰과

폼에 대한 유효성 검사 등 간단하면서도 강력한 기능들을 쉽게 수행할 수 있습니다.

💡 본 글은 예제 코드를 이용하여 설명합니다.

2. 폼

2.1. 폼 생성

로그인과 회원가입 예제를 통해 Flask-WTF 사용법을 익혀봅니다.

flask/source/my_app/forms/user.py를 살펴보면 FlaskForm을 상속받은 LoginForm 클래스와 RegistrationForm 클래스가 존재하는 것을 확인할 수 있습니다.

일반적으로 로그인에는 사용자의 아이디와 비밀번호가 필요할 것입니다.

아이디는 StringField로 지정하여 HTML의 type이 text인 input 태그로 받아드리고,

비밀번호는 PasswordField로 지정하여 HTML의 type이 password인 input 태그로 받아드리도록 지정하였습니다.

각각의 필드는 필수조건이기 때문에 검증을 수행하는 validators에 DataRequired 속성을 추가하였습니다.

마지막으로 로그인 버튼이 있어야 하기 때문에 SubmitField를 이용하였습니다.

class LoginForm(FlaskForm):
    username = StringField(
        label="아이디",
        validators=[
            DataRequired(message="아이디를 입력하세요."),
            Length(max=10, message="로그인에 실패하였습니다."),
            UsernameFilter(banned=[], regex=r"^[a-z0-9_]*$", message="로그인에 실패하였습니다.")
        ],
        render_kw={"autofocus": True, "placeholder": ""}
    )
    password = PasswordField(
        label="비밀번호",
        validators=[
            DataRequired(message="비밀번호를 입력하세요."),
            Length(max=15, message="로그인에 실패하였습니다.")
        ],
        render_kw={"placeholder": ""}
    )
    submit = SubmitField(label="로그인")

회원가입에는 로그인과 마찬가지로 아이디와 비밀번호가 존재하고, 추가적으로 비밀번호 확인이라는 필드를 추가하였습니다.

비밀번호와 비밀번호 확인이라는 값을 서로 같아야 하기 때문에 비밀번호 필드 중 validators에 EqualTo라는 검증 로직을 추가하였습니다.

class RegistrationForm(FlaskForm):
    username = StringField(
        label="아이디",
        validators=[
            DataRequired(message="아이디를 입력하세요."),
            Length(min=4, max=10, message="아이디는 4자 이상, 15자 이하여야 합니다."),
            UsernameFilter(banned=["admin"], regex=r"^[a-z0-9_]*$")
        ],
        render_kw={"autofocus": True, "placeholder": ""}
    )
    password = PasswordField(
        label="비밀번호",
        validators=[
            DataRequired(message="비밀번호를 입력하세요."),
            Length(min=8, max=15, message="비밀번호는 8자 이상, 15자 이하여야 합니다."),
            EqualTo(fieldname="password_confirm", message="비밀번호가 일치하지 않습니다.")
        ],
        render_kw={"placeholder": ""}
    )
    password_confirm = PasswordField(
        label="비밀번호 확인",
        validators=[
            DataRequired(message="비밀번호 확인을 입력하세요."),
            Length(min=8, max=15, message="비밀번호는 8자 이상, 15자 이하여야 합니다.")
        ],
        render_kw={"placeholder": ""}
    )
    submit = SubmitField(label="회원가입")

로그인과 회원가입의 아이디 부분에는 UsernameFilter라는 사용자 정의 검증로직을 추가한것을 확인할 수 있을것입니다.

아이디에 허용되는 문자 형식을 정규식을 이용하여 필터링하고 사용할 수 없는 아이디를 필터링하는 기능입니다.

class UsernameFilter:
    def __init__(self, banned:list, regex:str, message=None):
        self.banned = banned
        self.regex = regex
        
        if not message:
            message = "사용할 수 없는 아이디입니다."
        self.message = message

    def __call__(self, form, field):
        for ban in self.banned:
            if ban in field.data.lower():
                raise ValidationError(self.message)
        
        if not re.match(re.compile(self.regex), field.data):
            raise ValidationError(self.message)

2.2. 폼 렌더링

Flask-WTF을 이용하여 폼을 정의하였으니, HTML에 렌더링을 해야합니다.

flask/source/my_app/views/index.py를 살펴보면 LoginForm 객체를 생성하고 login.html에 LoginForm 객체를 전달합니다.

# 코드 생략
form=LoginForm()
    
if request.method == "GET":
	return render_template(
		template_name_or_list="user/login.html",
		form=form
	)

flask/source/my_app/templates/user/login.html는 로그인 폼을 렌더링하는 부분입니다.

index.py에서 전달한 객체를 {폼 변수}.{필드 변수}를 이용하여 사용하는것을 확인할 수 있습니다.

hidden_tag()는 Flask-WTF에서 제공하는 기능 중 하나로 CSRF 공격을 방어하기 위한 토큰 값입니다.

폼이 렌더링될 때, 추가하고 싶은 속성 값이 존재한다면 괄호(())를 이용하여 속성과 값을 아래의 예시와 같이 입력하면 됩니다.

아래의 예시는 bootstrap의 form과 관련한 class를 추가한 예제입니다.

<!-- 코드 생략 -->
<form action="{{ url_for('index.login') }}" method="post">
    {{ form.hidden_tag() }}
    <div class="form-label-group position-relative mb-3">
        {{ form.username(class="form-control") }}
        {{ form.username.label }}
    </div>

    <div class="form-label-group position-relative mb-3">
        <div class="input-group">
            {{ form.password(class="form-control") }}
            {{ form.password.label }}
            <span class="input-group-text" style="width: 46px;">
                <i class="fa-regular fa-eye cursor password-toggle"></i>
            </span>
        </div>
    </div>
    
    <div class="d-flex justify-content-between align-items-center">
        {{ form.submit(class="btn btn-lg btn-primary text-uppercase font-weight-bold") }}
        <a href="{{ url_for('index.regist') }}" class="text-primary">회원가입</a>
    </div>
</form>

2.3. 폼 처리

HTML에서 사용자가 입력한 데이터를 처리할 필요가 있습니다. 아래의 예제는 로그인 폼으로 부터 데이터를 전달받아 처리하는 예제입니다.

flask/source/my_app/views/index.py를 살펴보면 form.validate_on_submit() 함수를 사용한 것을 확인할 수 있습니다.

LoginForm을 정의 시 각 필드들의 validators에 추가한 검증 로직과 CSRF 토큰의 유효성을 검사하는 함수입니다.

# 코드 생략
if form.validate_on_submit():

클라이언트가 입력한 데이터를 통해 사용자 정보를 데이터베이스에서 조회하는 코드를 확일할 수 있습니다.

이때, 폼으로 부터 전달된 데이터는 {폼 변수}.{필드 변수}.data로 접근이 가능합니다.

# 코드 생략
user = User.query.filter_by(username=form.username.data).first()

else문은 폼 검증이 실패하였을 때, 분기되는 분기문입니다.

{폼 변수}.errors.items()로 에러 메시지를 확인 할 수 있습니다.

# 코드 생략
else:
	for _, values in form.errors.items():
		for value in values:
			flash(message=value)
profile
IT 도서관

0개의 댓글