πŸ“’'ν”ŒλΌμŠ€ν¬ μ›Ή 개발' 4μž₯, μ›Ή 폼

Jake_YoungΒ·2020λ…„ 7μ›” 31일
0
post-thumbnail
post-custom-banner

pip install flask-wtf
preventing from CSRF attack

크둜슀-μ‚¬μ΄νŠΈ λ¦¬ν€˜μŠ€νŠΈ μœ„μ‘°(CSRF) 보호

CSFR 곡격은 μ•…μ˜μ  μ›Ήμ‚¬μ΄νŠΈμ—μ„œ ν¬μƒμžκ°€ λ‘œκ·ΈμΈν•œ λ‹€λ₯Έ μ›Ήμ‚¬μ΄νŠΈλ‘œ λ¦¬ν€˜μŠ€νŠΈλ₯Ό 전솑할 λ•Œ μΌμ–΄λ‚œλ‹€.
이λ₯Ό μœ„ν•΄ Flask-WTFλŠ” μ•”ν˜Έν™” ν‚€λ₯Ό μ„€μ •ν•˜κΈ° μœ„ν•œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ ν•„μš”ν•˜λ‹€.
이 ν‚€λ₯Ό μ‚¬μš©ν•˜μ—¬ μ•”ν˜Έν™”λœ 토큰을 μƒμ„±ν•˜κ³  이 토큰은 폼 데이터와 ν•¨κ»˜ λ¦¬ν€˜μŠ€νŠΈ 인증을 κ²€μ¦ν•˜λŠ”λ° μ‚¬μš©λœλ‹€.

app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'

app.config λ”•μ…”λ„ˆλ¦¬λŠ” ν”„λ ˆμž„μ›Œν¬, ν™•μž₯ ν˜Ήμ€ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μžμ²΄μ—μ„œ μ‚¬μš©ν•˜λŠ” μ„€μ • λ³€μˆ˜λ“€μ„ μ €μž₯ν•˜κΈ° μœ„ν•΄ 일반적으둜 μ‚¬μš©ν•˜λŠ” 곡간이닀.

폼 클래슀

Flask-WTF을 μ‚¬μš©ν•  λ•Œ μ›Ή 폼은 Form ν΄λž˜μŠ€λ‘œλΆ€ν„° μƒμ†ν•œ ν΄λž˜μŠ€μ— μ˜ν•΄ ν‘œν˜„λœλ‹€.
폼에 μžˆλŠ” ν•„λ“œλŠ” 각각 였브젝트둜 ν‘œν˜„λœλ‹€.
각 ν•„λ“œλŠ” ν•˜λ‚˜ μ΄μƒμ˜ κ²€μ¦μž(validator)κ°€ λΆ™μ–΄ μžˆλ‹€.
κ²€μ¦μžλŠ” μ œμΆœν•œ μž…λ ₯값이 μ˜¬λ°”λ₯Έμ§€ ν™•μΈν•˜λŠ” ν•¨μˆ˜μ΄λ‹€.

from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required # NULL 값이 μ•„λ‹˜μ„ 보μž₯

class NameForm(Form):
  name = StringField('What is your name?', validator = [Required()])
  submit = SubmitField('Submit')

폼에 μžˆλŠ” ν•„λ“œλŠ” 클래슀 λ³€μˆ˜λ‘œ μ •μ˜λ˜λ©° 각 클래슀 λ³€μˆ˜λŠ” ν•„λ“œ νƒ€μž…κ³Ό κ΄€κ³„λœ 였브젝트둜 ν• λ‹Ήλœλ‹€.
예λ₯Ό λ“€μ–΄, StringField ν΄λž˜μŠ€λŠ” ν•­λͺ©μ„ type="text" μ†μ„±μœΌλ‘œ ν‘œν˜„ν•œλ‹€.

Form 베이슀 ν΄λž˜μŠ€λŠ” Flask-WTF ν™•μž₯에 μ˜ν•΄ μ •μ˜λ˜λ―€λ‘œ flask.ext.wtfμ—μ„œ μž„ν¬νŠΈλœλ‹€. κ·ΈλŸ¬λ‚˜ ν•„λ“œμ™€ κ²€μ¦μžλŠ” 직접 WTForms νŒ¨ν‚€μ§€λ‘œλΆ€ν„° μž„ν¬νŠΈλœλ‹€.

WTFormsμ—μ„œ μ§€μ›ν•˜λŠ” ν‘œμ€€ HTML ν•„λ“œμ˜ λ¦¬μŠ€νŠΈλŠ” μ•„λž˜μ™€ κ°™λ‹€

ν•„λ“œ νƒ€μž…μ„€λͺ…
StringFieldν…μŠ€νŠΈ
TextAreaField닀쀑 라인 ν…μŠ€νŠΈ
PasswordFieldνŒ¨μŠ€μ›Œλ“œ ν…μŠ€νŠΈ
HiddenFieldμˆ¨κ²¨μ§„ ν…μŠ€νŠΈ
DateFielddatetime.date ν…μŠ€νŠΈ
DateTimeFielddatetime.datetime ν…μŠ€νŠΈ
IntegerFieldμ •μˆ˜ κ°’μ˜ ν…μŠ€νŠΈ
DecimalFieldDecimal κ°’μ˜ ν…μŠ€νŠΈ
FloatFieldλΆ€λ™μ†Œμˆ˜μ μ„ κ°–λŠ” 수의 ν…μŠ€νŠΈ
BooleanFieldTrue or False
RadioFieldλΌλ””μ˜€ λ²„νŠΌ 리슀트
SelectField선택 κ°€λŠ₯ν•œ λ“œλ‘­λ‹€μš΄ 리슀트
SelectMultipleField닀쀑 선택 λ“œλ‘­λ‹€μš΄ 리슀트
FileField파일 μ—…λ‘œλ“œ ν•„λ“œ
SubmitField폼 제좜 λ²„νŠΌ
FormField폼 μ•ˆμ— 폼
FieldList주어진 νƒ€μž…μ˜ ν•„λ“œ 리슀트

WTForms에 λ‚΄μž₯λ˜μ–΄ μžˆλŠ” κ²€μ¦μžμ˜ λ¦¬μŠ€νŠΈλŠ” μ•„λž˜μ™€ κ°™λ‹€

κ²€μ¦μžμ„€λͺ…
Email이메일 μ£Όμ†Œ
EqualTo두 ν•„λ“œ κ°’ 비ꡐ (νŒ¨μŠ€μ›Œλ“œ 검증)
IPAddressIPv4 λ„€νŠΈμ›Œν¬ μ£Όμ†Œ
Lengthμž…λ ₯ν•œ λ¬Έμžμ—΄μ˜ 길이λ₯Ό 검증
NumberRangeμž…λ ₯ν•œ 값이 μˆ«μžμ™€ μ•ŒνŒŒλ²³ λ²”μœ„μΈμ§€ 검증
OptionalNull 값을 ν—ˆμš©ν•˜κ³  μΆ”κ°€ν•œ κ²€μ¦μžλ₯Ό κ±΄λ„ˆλœ€
RequiredNull 값을 λΆˆν—ˆ
Regexpμ •κ·œν‘œν˜„μ‹μ— λŒ€ν•œ μž…λ ₯을 검증
URLURL을 검증
AnyOfμž…λ ₯이 κ°€λŠ₯ν•œ κ°’λ“€ 쀑 ν•˜λ‚˜μΈμ§€λ₯Ό 검증
NoneOfμž…λ ₯이 λΆˆκ°€ν•œ κ°’λ“€ 쀑 ν•˜λ‚˜κ°€ μ•„λ‹ˆλΌλŠ” 것을 검증

폼의 HTML λ Œλ”λ§

폼 ν•„λ“œλŠ” 호좜이 κ°€λŠ₯ν•˜λ‹€.
Flask-Bootstrap은 전체 Flask-WTF 폼을 λ Œλ”λ§ν•˜κΈ° μœ„ν•΄ λΆ€νŠΈμŠ€νŠΈλž©μ˜ 미리 μ •μ˜λœ 폼 μŠ€νƒ€μΌμ„ μ‚¬μš©ν•˜λ„λ‘ μƒλ‹Ήν•œ μƒμœ„ 레벨의 헬퍼 ν•¨μˆ˜λ₯Ό μ œκ³΅ν•œλ‹€.

<form method="POST">
  {{ form.name.label }} {{ form.name() }}
  {{ form.submit() }}
</form>

<!-- μœ„μ•„λž˜λŠ” 같은 것이닀 -->

{% import "bootstrap/wtf.html' as wtf %}
{{ wtf.quick_form(form) }}

λ·° ν•¨μˆ˜μ—μ„œμ˜ 폼 처리

app.route에 methodsλ₯Ό λ“±λ‘ν•˜μ§€ μ•ŠμœΌλ©΄, ν•΄λ‹Ή λ·° ν•¨μˆ˜λŠ” GET λ¦¬ν€˜μŠ€νŠΈλ§Œ μ²˜λ¦¬ν•œλ‹€.
validate_on_submit()은 폼이 제좜될 λ•Œ Trueλ₯Ό λ°˜ν™˜ν•œλ‹€.

@app.route('/', methods=['GET','POST']
def index():
  name = None
  form = NameForm()
  if form.validate_on_submit():
    name = form.name.data
    form.name.data = ''
  return render_template('index.html', form=form, name=name)

λ¦¬λ‹€μ΄λ ‰νŠΈμ™€ μ‚¬μš©μž μ„Έμ…˜

μ›Ή μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ λΈŒλΌμš°μ €μ— μ˜ν•΄ μ „μ†‘λœ λ§ˆμ§€λ§‰ λ¦¬ν€˜μŠ€νŠΈλ‘œ POST λ¦¬ν€˜μŠ€νŠΈλ₯Ό 남겨 두지 μ•Šλ„λ‘ ν•˜λŠ” μŠ΅κ΄€μ„ 듀일 ν•„μš”κ°€ μžˆλ‹€. (POST/REDIRECT/GET Pattern)

이 μŠ΅κ΄€μ€ 정상적인 응닡 λŒ€μ‹ μ— λ¦¬λ‹€μ΄λ ‰νŠΈμ™€ ν•¨κ»˜ POST λ¦¬ν€˜μŠ€νŠΈμ— λŒ€ν•΄ μ‘λ‹΅ν•˜λŠ” 것이닀.
그렇지 μ•ŠμœΌλ©΄ 이전에 μ œμΆœν•œ requestλ₯Ό νŽ˜μ΄μ§€κ°€ κΈ°μ–΅ν•˜μ—¬ μƒˆλ‘œκ³ μΉ¨μ„ μ‹œν–‰ν•  μ‹œ, λ‹€μ‹œ ν•œλ²ˆ λ˜‘κ°™μ€ λ¦¬ν€˜μŠ€νŠΈκ°€ μ œμΆœλ˜λŠ” 문제λ₯Ό μΌμœΌν‚¨λ‹€.
이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•˜μ—¬ POST/REDIRECT/GET νŒ¨ν„΄μœΌλ‘œ 문제λ₯Ό ν•΄κ²°ν•  수 μžˆλ‹€.
그런데 이 λ˜ν•œ 기쑴에 μ €μž₯된 μ‚¬μš©μž 정보λ₯Ό μžƒμ–΄λ²„λ¦΄ 수 μžˆλ‹€λŠ” 문제점이 μžˆλ‹€.
이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ μ‚¬μš©μž μ„Έμ…˜μ„ μ‚¬μš©ν•  수 μžˆλ‹€.

#hello.py
from flask import Flask, render_template, session, redirect, url_for

@app.route('/', methods=['GET','POST'])
def index():
  form = NameForm()
  if form.validate_on_submit():
    session['name'] = form.name.data
    return redirect(url_for('index'))
  return render_template('index.html', form=form, name=session.get('name'))

λ©”μ‹œμ§€ ν”Œλž˜μ‹±

λ•Œλ‘œλŠ” λ¦¬ν€˜μŠ€νŠΈλ₯Ό μ™„λ£Œν•˜κ³  λ‚˜μ„œ μ‚¬μš©μžμ—κ²Œ μƒνƒœ μ—…λ°μ΄νŠΈλ₯Ό μ „λ‹¬ν•˜λŠ” 것이 μœ μš©ν•˜λ‹€.
μ΄λŠ” 둜그인 μ‹œλ„μ— λ”°λ₯Έ 응닡 λ©”μ‹œμ§€λ₯Ό 전달해야 ν•˜λŠ” κ²½μš°μ΄λ‹€.
이λ₯Ό μœ„ν•΄ ν”ŒλΌμŠ€ν¬λŠ” flash() ν•¨μˆ˜λ₯Ό μ œκ³΅ν•œλ‹€.

from flask import Flask, render_template, session, redirect, url_for, flash

@app.route('/'. methods=['GET','POST']
def index():
  form = NameForm()
  if form.validate_on_submit():
    old_name = session.get('name')
    if old_name is not None and old_name != form.name.data:
      flash('Looks like you have changed your name!')
    session['name'] = form.name.data
    form.name.data = ''
    return redirect(url_for('index'))
  return render_template('index.html', form = form, name = session.get('name'))

그런데 μ΄λ ‡κ²Œ flash()λ₯Ό ν˜ΈμΆœν•˜λŠ” κ²ƒλ§ŒμœΌλ‘œλŠ” 메세지λ₯Ό 좜λ ₯ν•˜κΈ°μ— μΆ©λΆ„ν•˜μ§€ μ•Šλ‹€.
베이슀 ν…œν”Œλ¦Ώμ„ ν™œμš©ν•˜μ—¬ λͺ¨λ“  νŽ˜μ΄μ§€μ—μ„œ λ³΄μ—¬μ§ˆ 수 μžˆλ„λ‘ λ§Œλ“€μ–΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.
get_flashed_messages() ν•¨μˆ˜λŠ” 이λ₯Ό 도와쀀닀.

<!-- template/base.html -->
{% block content %}
<div class='container'>
  {% for message in get_flashed_messages() %}
  <div class="alert alert-warning">
    <button type="button" class="close" data-dismiss="alert">
      &times;
    </button>
    {{ message }}
  </div>
  {% endfor %}
  
  {% block page_content %}
  {% endblock %}
</div>
{% endblock %}
profile
μžλ°”μŠ€ν¬λ¦½νŠΈμ™€ 파이썬 그리고 컴퓨터와 λ„€νŠΈμ›Œν¬
post-custom-banner

0개의 λŒ“κΈ€