I八孝、一個綜上小結(jié)的實例--轉(zhuǎn)賬demo
需求:用戶通過username = admin插掂,password = 1234登錄轉(zhuǎn)賬界面灰瞻,點擊轉(zhuǎn)賬按鈕后提示轉(zhuǎn)賬xxx元到xxx賬戶成功
分析:此處我們應用wtf和cookie來進行驗證用戶登錄,然后用兩個視圖函數(shù)模擬轉(zhuǎn)賬
首先辅甥,根據(jù)創(chuàng)建一個app-transferdemo酝润,根據(jù)以往的經(jīng)驗我們先導入幾個包
from flask import Flask, request, render_template, redirect, url_for, flash, make_response
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, EqualTo
app = Flask(__name__)
if __name__ == '__main__':
app.run(port=5003, debug=True)
其中make_response
相當于Django內(nèi)的Httpresponse
繼而,根據(jù)wtf的驗證方式璃弄,我們需要定義一個登陸驗證表單
class RegisterForm(FlaskForm):
username = StringField("用戶名:", validators=[DataRequired("請輸入用戶名")], render_kw={"placeholder": "請輸入用戶名"})
password = PasswordField("密碼", validators=[DataRequired("請輸入密碼"),render_kw={"placeholder": "請輸入密碼"}])
submit = SubmitField("登陸")
編寫相應的登錄界面logintrans.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
{{ form.username.label }}{{ form.username }}<br>
{{ form.password.label }}{{ form.password }}<br>
{{ form.submit }}<br>
{{ form.csrf_token }}
</form>
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %}
</body>
</html>
編寫登錄驗證的視圖函數(shù)index
@app.route('/', methods=["get", "post"])
def index():
register_form = RegisterForm()
if register_form.validate_on_submit():
# 如果代碼走到if里面證明表單驗證有效
username = request.form.get("username")
password = request.form.get("password", "")
if username == 'admin' and password == '1234':
return redirect(url_for('transfer'))
else:
if request.method == 'POST':
flash("表單參數(shù)有誤或者不完整")
return render_template("logintrans.html", form=register_form)
在登錄后要销,跳轉(zhuǎn)到transfer頁面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>轉(zhuǎn)賬頁面</title>
</head>
<body>
<form action="" method="post">
<label>賬戶</label><input type="text" name="account">
<label>金額</label><input type="number" name="money">
<input type="submit" value="轉(zhuǎn)賬">
</form>
</body>
</html>
編寫相應的transfer視圖函數(shù)
@app.route('/transfer',methods=["get", "post"])
def transfer():
# 取出cookie確保登錄
username = request.cookies.get('username', '')
if not username:
# 無cookie說明未登錄,需要登錄
return redirect(url_for('index'))
if request.method == 'POST':
account = request.form.get('account')
money = request.form.get('money')
return '轉(zhuǎn)賬{}元到{}賬戶成功'.format(account, money)
# makeresponse 相當于Django內(nèi)的Httpresponse
response = make_response(render_template('transfer.html'))
return response
啟動服務夏块,觀察效果
II疏咐、CSRF攻擊:跨站請求偽造
首先我們創(chuàng)建兩個應用weba、webb脐供,基于轉(zhuǎn)賬功能浑塞,此處我們不應用wtf驗證
weba.py
from flask import Flask, request, render_template, redirect, url_for, flash, make_response
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, EqualTo
app = Flask(__name__)
# 自定義一個表單類
class RegisterForm(FlaskForm):
username = StringField("用戶名:", validators=[DataRequired("請輸入用戶名")], render_kw={"placeholder": "請輸入用戶名"})
password = PasswordField("密碼", validators=[DataRequired("請輸入密碼")])
password2 = PasswordField("確認密碼", validators=[DataRequired("請輸入確認密碼"), EqualTo("password", "兩次密碼不一致")])
submit = SubmitField("注冊")
app.secret_key = 'dasdasdsadas'
@app.route('/', methods=["get", "post"])
def index():
if request.method == 'POST':
# 取出表單數(shù)據(jù)
username = request.form.get("username")
password = request.form.get("password", "")
password2 = request.form.get("password2", "")
if not all([username, password]):
print('參數(shù)error')
else:
print(password, username)
if username == 'admin' and password == '1234':
# 登錄成功跳轉(zhuǎn)轉(zhuǎn)賬頁面
response = redirect(url_for('transfer'))
response.set_cookie('username', username)
return response
else:
print('password error')
return render_template("login2.html")
@app.route('/transfer',methods=["get", "post"])
def transfer():
# 取出cookie確保登錄
username = request.cookies.get('username', '')
if not username:
# 無cookie說明未登錄,需要登錄
return redirect(url_for('index'))
if request.method == 'POST':
account = request.form.get('account')
money = request.form.get('money')
return '轉(zhuǎn)賬{}元到{}賬戶成功'.format(account, money)
# makeresponse 相當于Django內(nèi)的Httpresponse
response = make_response(render_template('transfer.html')
)
return response
if __name__ == '__main__':
app.run(port=5003, debug=True)
login2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
<label>用戶名</label><input type="text" name="username">
<label>密碼</label><input type="password" name="password">
<input type="submit" value="登錄">
</form>
</body>
</html>
webb.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/', methods=["get", "post"])
def index():
return render_template('csrf-index.html')
if __name__ == '__main__':
app.run(port=5004, debug=True)
csrf-index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>襲擊網(wǎng)站A</title>
</head>
<body>
<form action="" method="post">
<input type="hidden" name="account" value="999">
<input type="hidden" name="money" value="111">
<input type="submit" value='點擊領取優(yōu)惠券'>
</form>
</body>
</html>
在建立好如上應用后政己,我們嘗試模擬csrf攻擊酌壕,由webb攻擊weba
由于weba是一個登錄后的轉(zhuǎn)賬頁面,為了攻擊該轉(zhuǎn)賬過程匹颤,嘗試修改csrf-index.html內(nèi)form
的action
<form action="http://127.0.0.1:5003/transfer" method="post">
啟動兩個app仅孩,清除網(wǎng)站的cookie,然后點擊點擊領取優(yōu)惠券
該按鈕查看過程印蓖。
我們可以發(fā)現(xiàn)辽慕,當admin未登錄時,點擊按鈕返回登錄界面赦肃。當用戶admin登錄后溅蛉,我們點擊按鈕時公浪,會提示轉(zhuǎn)賬信息,而該轉(zhuǎn)賬信息并未來自于admin用戶船侧,而如何防治這種攻擊呢欠气?
我們可以利用csrf_token的方式進行校驗
首先我們編輯生成csrf_token的方法csrf_g.py
import base64
import os
def generate_csrf():
return bytes.decode(base64.b64encode(os.urandom(48)))
print(generate_csrf())
在每次轉(zhuǎn)賬前都生成一個csrf_token
使得每次生成的csrf_token
值都是不一樣的,利用瀏覽器的無法跨站請求cookie的協(xié)議镜撩,webb無法請求weba的cookie
修改weba內(nèi)的transfer視圖预柒,添加驗證csrf_token的內(nèi)容:
import csrf_g
def transfer():
# 取出cookie確保登錄
username = request.cookies.get('username', '')
if not username:
# 無cookie說明未登錄,需要登錄
return redirect(url_for('index'))
if request.method == 'POST':
account = request.form.get('account')
money = request.form.get('money')
# 取表單中的csrf_token
form_csrf_token = request.form.get('csrf_token')
# 取cookie內(nèi)csrf_token
cookie_csrf_token = request.cookies.get('csrf_token')
if form_csrf_token != cookie_csrf_token:
return "非法操作"
else:
return '轉(zhuǎn)賬{}元到{}賬戶成功'.format(account, money)
csrf_token = csrf_g.generate_csrf()
# makeresponse 相當于Django內(nèi)的Httpresponse
response = make_response(render_template('transfer.html', csrf_token=csrf_token))
response.set_cookie('csrf_token', csrf_token)
return response
而在多數(shù)實際操作的過程中袁梗,我們不必過于麻煩宜鸯,可以使用flask已封裝的方法,即CsrfProtect
模塊遮怜,此處我們應用的是Flask 1.1.2淋袖。
我們找到之前我們用wtf表單進行的登錄驗證app,利用CsrfProtect
模塊添加驗證內(nèi)容
from flask_wtf.csrf import CSRFProtect, CSRFError
CSRFProtect(app)
# 添加錯誤裝飾器
@app.errorhandler(CSRFError)
def csrf_error(reason):
return render_template('csrf_error.html', reason=reason), 400
新建csrf_error.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
ERROR
</body>
</html>
修改wtf.html
<form action="" method="post">
{{ form.username.label }}{{ form.username }}<br>
{{ form.password.label }}{{ form.password }}<br>
{{ form.password2.label }}{{ form.password2 }}<br>
{{ form.submit }}<br>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
</form>
實現(xiàn)csrf保護
而對于添加csrf保護的方法不僅僅于此锯梁,具體內(nèi)容可通過官方文檔 CsrfProtect深入了解
III即碗、關聯(lián)數(shù)據(jù)庫方式SQLAlchemy
在學習完之前的預備內(nèi)容后,我們大致了解了Flask相關的一些核心內(nèi)容陌凳,而我們接下來要繼續(xù)深入了解Flask剥懒,而這就不得不了解Flask的ORM框架結(jié)構(gòu),因此冯遂,我們需要先體會一下Flask內(nèi)管理數(shù)據(jù)庫的方式蕊肥。在此處我們僅僅做一個引例谒获,在下一章中將繼續(xù)深入了解該內(nèi)容蛤肌。
在引例子中,我們將涉及一些關聯(lián)數(shù)據(jù)庫的基礎操作批狱,請自行體會裸准。
預備過程需要安裝 flask_sqlalchemy、mysqlclient并創(chuàng)建一個pure python 的項目test4赔硫,創(chuàng)建數(shù)據(jù)庫flask_test
編寫一個db_demo.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# 設置數(shù)據(jù)庫連接
# 設置格式app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://用戶名:密碼@數(shù)據(jù)庫端口地址/數(shù)據(jù)庫名稱'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:123456@127.0.0.1:3306/test_flask'
# 動態(tài)追蹤設置炒俱,可用于關閉某些Warning
app.config['SQLALCHEMY_TRACK_MODUFICATIONS'] = True
# 顯示原始sql
app.config['SQLALCHEMY_ECHO'] = True
# 設置每次請求后自動提交數(shù)據(jù)庫的改動
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
# 數(shù)據(jù)庫要和 app關聯(lián),db為SQLAlchemy的一個實例
db = SQLAlchemy(app)
# 創(chuàng)建一個角色類作為例子爪膊,繼承于db
class Role(db.Model):
# 定義表名
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
@app.route('/')
def index():
return 'index'
if __name__ == '__main__':
# 刪除表
db.drop_all()
# 創(chuàng)建表
db.create_all()
ro1 = Role(name='admin')
ro2 = Role(name='user')
db.session.add_all([ro1, ro2])
db.session.commit()
app.run(port=5005)
注:Flask在創(chuàng)建表時并不會自動創(chuàng)建id主鍵值和表名权悟,需要自行設計,這與Django是有區(qū)別的
運行服務后推盛,打開數(shù)據(jù)庫峦阁,觀察結(jié)果。