Flask 中 AJAX 认证的 CSRF 保护
我想在一个网站上把登录和注册表单做成AJAX形式。到目前为止,我主要使用WTForms,因为它自带CSRF保护,但在这个项目中,我觉得没必要用它——这只是增加了一层复杂性,让原本应该很简单的事情变得麻烦。
于是我在Flask的安全部分找到了这个代码片段:
@app.before_request
def csrf_protect():
if request.method == "POST":
token = session.pop('_csrf_token', None)
if not token or token != request.form.get('_csrf_token'):
abort(403)
def generate_csrf_token():
if '_csrf_token' not in session:
session['_csrf_token'] = some_random_string()
return session['_csrf_token']
app.jinja_env.globals['csrf_token'] = generate_csrf_token
我理解这段代码背后的思路。其实,这一切对我来说都很有道理(我觉得)。我看不出有什么问题。
但是它并不工作。我唯一改动的就是把伪函数some_random_string()
替换成了os.urandom(24)
的调用。到目前为止,每次请求都返回403错误,因为token
和request.form.get('_csrf_token')
从来不一样。当我打印它们的时候,这一点就很明显——通常它们是不同的字符串,但偶尔又会出现一个是None
或者是os.urandom(24)
输出的截断版本。显然有什么地方不对劲,但我不明白是什么原因。
2 个回答
3
我觉得你的问题出在os.urandom这个函数上。这个函数的结果可能包含一些在html中无法正确解析的符号。所以当你把csrf_token放进html里而没有进行任何处理时,就会出现你描述的问题。
怎么解决。 试着在html中对csrf_token进行处理 (查看文档),或者使用其他方法来生成csrf_token。例如,可以使用uuid:
import uuid
...
def generate_random_string():
return str(uuid.uuid4())
...
5
你可以享受到 flask-wtf
带来的便利,但不需要它那么复杂,也不用自己重新实现一遍:
from flask_wtf.csrf import CsrfProtect
然后在初始化的时候,可以选择:
CsrfProtect(app)
或者:
csrf = CsrfProtect()
def create_app():
app = Flask(__name__)
csrf.init_app(app)
这个令牌(token)在应用的任何地方都可以使用,包括通过 jinja2
来访问:
<form method="post" action="/">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
</form>
(详细信息可以查看 文档)