跨站请求伪造保护
- Flask-WTF
Flask-WTF能保护所有表单免受跨站请求伪造的攻击。为了实现CSRF防护,Flask-WTF需要为程序配置一个密钥。Flask-WTF使用这个密钥生成加密令牌,再用令牌验证请求中表单数据的真伪。
- 设置密钥
使用类来存储配置变量,项目结构清晰,同时密钥保存在环境变量中,也增强了安全性。
touch app/config.py
# config.py
import os
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or 'marksecret'
配置好密钥,需要在Flask读取并使用。可以在生成Flask应用之后,利用app.config.from_object()方法来完成这个操作。
# __init__.py
from flask import Flask
from config import Config
app = Flask(__name__)
app.config.from_object(Config)
from app import routes
- 验证密钥
>>> from app import *
>>> app.config['SECRET_KEY']
'marksecret'
表单类
touch app/forms.py
# forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired
class LoginForm(FlaskForm):
username = StringField('username', validators=[DataRequired()])
password = PasswordField('password', validators=[DataRequired()])
remember_me = BooleanField('Remember Me')
submit = SubmitField('Sign In')
把表单渲染成HTML
<!--login.html-->
{% extends "base.html" %}
{% block content %}
<h1>Sign In</h1>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}<br>
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=32) }}
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>{{ form.remember_me()}} {{ form.remember_me.label }}</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %} }
在视图函数中处理表单
# routes.py
from app import app
from flask import render_template,flash,redirect,url_for
from app.forms import LoginForm
@app.route('/')
@app.route('/user/<name>')
def user(name):
return '<h1>Hello, %s</h1>' % name
@app.route('/index')
def index():
user = {'username':'mark'}
posts = [
{
'author':{'username':'mark1'},
'body':'test1'
},
{
'author':{'username':'mark2'},
'body':'test2'
}
]
return render_template('index.html',title='home',user=user,posts=posts)
@app.route('/login',methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
flash('login requested for user {},remember_me={}'.format(form.username.data, form.remember_me.data))
return redirect(url_for('index'))
return render_template('login.html',title='sign in',form=form)
重定向
# 超链接
<a href="/index">Home</a>
<a href="/login">login</a>
# redirect()
return redirect('/index')
# url_for()
<a href="{{ url_for('index')}}">Home</a>
<a href="{{ url_for('login')}}">login</a>
# redirect()和url_for()
return redirect(url_for('index'))
Flash消息
请求完成后,用户需要知道状态发生了变化。这里可以使用确认消息、警告或者错误提醒。flash()
函数是向用户显示消息的有效途径。模板需要将消息渲染到基础模板中,才能让所有派生出来的模板都能显示出来。
flash('login requested for user {},remember_me={}'.format(form.username.data, form.remember_me.data))
# base.html
<!DOCTYPE html>
<html>
<head>
{% if title %}
<title>{{ title }} - mark</title>
{% else %}
<title>welcome to mark's blog</title>
{% endif %}
</head>
<body>
<div>mark's blog
<a href="{{ url_for('index')}}">Home</a>
<a href="{{ url_for('login')}}">login</a>
</div>
<hr>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</body>
</html>