Flask實現CSRF保護
參考官方文件
CSRF跨站請求偽造,源於WEB的隱式身份驗證機制,WEB的身份驗證機制雖然可以抱著一個請求時來自於某個使用者的瀏覽器,但卻無法保證該請求是使用者批准傳送的。
例如,使用者登入受信任的網址A,在本地生成了Cookie,在Cookie沒有失效的情況下去訪問了危險網站B,B可能會盜用你的身份,以你的名義去傳送惡意請求,郵件,盜取你的賬號,設定購買商品,造成你個人隱私洩露,已經財產安全。
實現
為了能夠讓所有的檢視受到CSRF保護,需要擴充套件
from flask_wtf.csrf import CsrfProtect
CsrfProtect(app)
注意,需要為CSRF保護設定一個金鑰,但通常情況下,和Flask應用的SECRET_KEY是一樣的。
如果你設定的模板中存在表單,你只需要在表單中新增如下
<form method="post" action="/">
{{ form.csrf_token }}
</form>
如果沒有模板中沒有表單,你仍然需要一個 CSRF 令牌:
<form method="post" action="/">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
</form>
如果網站沒有通過CSRF驗證,都會返回400響應,我們可以自定義這個錯誤響應:
from flask_wtf.csrf import CsrfProtect
csrf = CsrfProtect()
@csrf.error_handler
def csrf_error(reason):
return render_template('csrf_error.html', reason=reason), 400
這裡官方強烈建議對所有檢視啟用CSRF保護,也提供了給某些檢視函式不需要保護的裝飾器:
@csrf.exempt
@app.route('/foo', methods=('GET', 'POST'))
def my_handler():
(這裡csrf是CsrfProtect()的一個拓展物件)
你也可以在所有的檢視中禁用CSRF保護,app.config中通過設定 WTF_CSRF_CHECK_DEFAULT
為 False,僅僅當你需要的時候選擇呼叫csrf.protect(). 這樣能夠讓你在檢查CSRF令牌前做一些預處理:
@app.before_request
def check_csrf():
if not is_oauth(request):
csrf.protect()
AJAX
不用表單,通過ajax傳送post請求成為可能,flask 0.9後的版本可用。如果你使用了 CsrfProtect(app)
,你可以通過 {{ csrf_token() }}
獲取 CSRF 令牌。這個方法在每個模板中都可以使用,你並不需要擔心在沒有表單時如何渲染 CSRF 令牌欄位。
官方推薦在<meta>標籤中渲染CSRF令牌:
<meta name="csrf-token" content="{{ csrf_token() }}">
在<script>標籤中渲染同樣可以:
<script type="text/javascript">
var csrftoken = "{{ csrf_token() }}"
</script>
無論什麼時候傳送Ajax POST請求,都要新增X-CSRFToken請求頭
實現案例
view.py中程式碼案例
from flask_wtf.csrf import CSRFProtect
CSRFProtect(app)
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
# 獲取傳過來的資料, 這裡不是用表單的方式提交,所以用的 request.values.get("key") ,
# 如果是表單的方式提交資料的話,需要用request.form["key"] 或者 request.form.get("key")
name = request.values.get("name")
pwd = request.values.get("pwd")
# models查詢操作,到資料庫中查詢滿足條件的資料,first()返回查詢結果的第一條資料,實際是一個aa物件
flag = aa.query.filter(aa.username == name, aa.password == pwd).first()
print(flag)
if flag != None:
return make_response("{}")
return make_response("")
return render_template("login.html")
這裡點選登入後,會到aa中去查詢是否存在,然後返回不同的response,
login.html中的Ajax程式碼以及對應的表格,這裡我沒有使用表單
var csrftoken = $("meta[name=csrf-token]").attr("content");
if(flag){
//判斷賬號密碼是否匹配
$.ajax({
url:"/login",
type:"POST",
dataType:"json",
data:{"name":username, "pwd":pwd,},
headers:{"X-CSRFToken":csrftoken},
success:function (data) {
if (data!=null && data!=""){
location.href="/index";
}else {
texta.innerText="賬號或密碼錯誤"
}
},
error:function (data) {
alert("+++"+data.msg);
}
});
}
<table width="500px" border="1" cellspacing="0" cellpadding="0" align="center">
<meta name="csrf-token" content="{{ csrf_token() }}">
<tr height="50px">
<th colspan="2" style="font-size: 30px;" >登入圖書管理系統</th>
</tr>
<tr>
<td style="background-color: cornflowerblue;">使用者名稱:</td>
<td>
<input id="username" type="text" name="username" value="">
</td>
</tr>
<tr>
<td style="background-color: cornflowerblue;">密碼:</td>
<td>
<input id="pwd" type="password" name="pwd" value="">
</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="button" value="登入" onclick="login()"/>
</td>
</tr>
<tr>
<td colspan="2">
<a id="a" style="color: red"></a>
</td>
</tr>
</table>
使用ajax,則必須在其中新增 headers:{"X-CSRFToken":csrftoken},否則就會下圖所示錯誤
會提示CSRF令牌缺失,
這裡就是我關於flask中新增CSRF保護後,關於Ajax POST提交問題所遇到的問題,解決方法就是新增X-CSRFToken請求頭