参考官方文档
CSRF跨站请求伪造,源于WEB的隐式身份验证机制,WEB的身份验证机制虽然可以抱着一个请求时来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的。
例如,用户登录受信任的网址A,在本地生成了Cookie,在Cookie没有失效的情况下去访问了危险网站B,B可能会盗用你的身份,以你的名义去发送恶意请求,邮件,盗取你的账号,设置购买商品,造成你个人隐私泄露,已经财产安全。
实现
为了能够让所有的视图受到CSRF保护,需要扩展 CsrfProtect模块
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请求头