플라스크로 나만의 웹 페이지 만들기-04

Intro


이 포스트는 플라스크로 나만의 웹 페이지 만들기 - 03 에 의존합니다. 이전 포스트를 먼저 보시는걸 추천합니다.


Intro

CSRF란?


사이트 간 요청 위조(Cross-Site Request Forgery)의 약자로 웹 애플리케이션 취약점 중 하나.
사용자의 의지와 무관하게 공격자가 의도한 행동을 하여 특정 웹 페이지를 보안에 취약하게 하거나 수정, 삭제등을 하게 만드는 공격방법. 개인정보 유출사건의 대부분이 이 CSRF를 통해 관리자 계정을 탈취하여 악용하는 사례들 이다. 공격의 난이도가 높지않아 널리 사용되는 방법.


웹 폼


폼(Form)이란 사용자와 웹 사이트 또는 애플리케이션이 서로 상호작용할수 있도록 웹 사이트에 데이터를 전송하는 기능을 맡고있다.
일반적으로 웹 사이트에서 텍스트를 작성하는 필드와 제출하는 버튼, 선택하는 체크박스나 라디오버튼 등이 모두 폼 데이터 이다.

사용자 경험(UX)의 관점에서 폼이 많을수록 사용자들이 더 줄어들기 때문에 반드시 필요한 것이 아니라면 무분별하게 폼 필드를 삽입하는것은 좋지 않다.

플라스크에서는 Flaks-WTF확장을 통해 크로스 사이트 리퀘스트 위조(CSRF) 공격으로부터 모든 폼을 보호한다.
다음과 같은 명령으로 설치한다.

$ pip install flask-wtf

CSRF 보호를위해 Flask-WTF는 암화화 키를 설정하기위한 애플리케이션이 필요하다. Flask-WTF는 이 키를 사용하여 암호화된 토큰을 생성하고 이 토큰은 폼 데이터와 함께 리퀘스트(요청) 인증을 검증하는 데 사용된다. 암호화 키는 다음과같이 설정한다.

app = Flask(__name__)

app.config['SECRET_KEY'] = '어려운 암호 키'

SECRET_KEY로 지정한 암호 키가 복잡해 질수록 보안성이 높아진다는걸 알아두자.


폼 클래스


Flask-WTF를 사용할때 각 폼 필드 오브젝트는 하나 이상의 검증자(validator)가 붙어있다. 검증자는 사용자가 제출한 입력값이 올바른지 체크하는 함수이다.

간단한 예제로

from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required

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

텍스트 입력과 제출 버튼 하나를 가지는 예제이다.

여기서 validators의 인수로 Required()가 왔는데 이 함수는 필드가 빈 공간으로 제출되지 않도록 한다. 이런 검증자의 종류는 Email 검증부터 URL을 검증하는 것 까지 다양하다.


폼의 HTML 렌더링


폼 필드는 호출이 가능하다. 폼 필드가 호출되면 템플릿은 다음과 같이 간단한 HTML폼을 생성한다.


<form>
  {{ form.name.label }} {{ form.name() }}
  {{ form.submit() }}
</form>

프런트에 조금 관심이 있다면 Bootstrap도 공부해보자. 초라한 페이지가 화려해 지도록 Flask-Bootstrap을 사용했다. templates/index.html


{% import "bootstrap/wtf.html" as wtf %}

{% block title %}Flask_tutorial{% endblock %}

{% block page_content %}
<div class="page-header">
  <h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}</h1>
</div>
{{ wtf.quick_form(form) }}
{% endblock %}

Jinja2의 조건문 포멧을 사용한것을 눈치 챘다면 당신은 개발자!


뷰 함수에서의 폼 처리


hello.py의 뷰 함수를 새롭게 고쳐보자.

@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)

메소드 리스트에 GET뿐만이니라 POST도 추가하는 이유는 GET 리퀘스트에는 본문이 없고, 데이터는 쿼리 문자열로 URL에 그대로 노출되기 때문에 보안상에도 좋지않기 때문이다.

코드 설명

name변수는 폼으로부터 받은 이름을 저장하는 데 사용되고 이름을 알지못하면(처음 실행할 경우) None으로 초기화된다. form.validate_on_submit() 메소드는 폼이 서브밋될 때 True를 리턴하고 다른경우라면 False를 리턴한다.
따라서 사용자가 처음으로 애플리케이션을 사용한다면, 서버는 False를 리턴할 것이고 If문의 코드는 실행하지 않고 넘어가게 되며 바로 템플릿을 렌더링하도록 처리될 것이다.


다음포스트 - 플라스크로 나만의 웹 페이지 만들기 - 05