Flask 中 AJAX 认证的 CSRF 保护

6 投票
2 回答
7305 浏览
提问于 2025-04-29 07:59

我想在一个网站上把登录和注册表单做成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错误,因为tokenrequest.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>

(详细信息可以查看 文档

撰写回答