【问题标题】:Flask-WTF: CSRF token missingFlask-WTF:缺少 CSRF 令牌
【发布时间】:2017-01-08 16:07:17
【问题描述】:

看起来像一个简单的错误 - 由于“CSRF 令牌丢失”错误而无法完成的表单提交 - 已经变成了一天的头发拉扯。我浏览了每一篇与 Flask 或 Flask-WTF 相关的 SO 文章以及缺少 CSRF 令牌,但似乎没有任何帮助。

以下是详细信息:

Martijin's guidelines 之后回答之前的问题:

Flask-WTF CSRF 基础设施在以下情况下拒绝令牌:

1) 令牌丢失。这里不是这样,你可以在表单中看到token。

令牌肯定存在于我的表单中,并且已成功发布

2) 它太旧了(默认过期设置为 3600 秒,或一小时)。 在表单上设置 TIME_LIMIT 属性以覆盖它。可能不是 案例在这里。

对我来说也可以 - 令牌在默认到期时间之内

3) 如果在当前会话中没有找到 'csrf_token' 密钥。你可以 显然看到了会话令牌,所以也出来了。

就我而言,会话['csrf_token'] 已正确设置并被 Flask 看到

4) 如果 HMAC 签名不匹配;签名是基于 在 'csrf_token' 键下的会话中设置的随机值, 服务器端密钥,以及令牌中的到期时间戳。

这是我的问题。提交表单的 CSRF 和会话 CSRF 之间的 HMAC 比较失败。但我不知道如何解决它。我已经非常绝望(与其他提问者一样)深入研究 Flask-WTF 代码并设置调试消息以了解发生了什么。据我所知,它的工作原理是这样的:

1) "form.py" (Flask-WTF) 中的generate_csrf_token() 想要生成一个 CSRF 令牌。所以它调用:

2) generate_csrf() 在“csrf.py”中。如果不存在,该函数会生成一个新会话['csrf_token']。 在我的情况下,这种情况总是会发生 - 尽管其他会话变量似乎在请求之间持续存在,但我的调试显示我从不在我的会话开始时有一个“csrf_token”一个要求。这正常吗?

3) 当我在模板上渲染隐藏字段时,生成的标记被返回并可能合并到表单变量中。 (再次,调试表明此令牌存在于表单中并正确提交和接收)

4) 接下来,提交表单。

5) 现在调用 csrf.py 中的validate_csrf。但是由于另一个请求已经发生,并且 generate_csrf() 已经生成了一个新的会话 CSRF 令牌,这两个令牌的两个时间戳(在会话中和来自表单)将不匹配。由于 CSRF 部分由到期日期组成,因此验证失败。

我怀疑问题出在第 2 步,即为每个请求生成一个新令牌。但我不知道为什么我的会话中的其他变量在请求之间持续存在,而不是“csrf_token”。

SECRET_KEY 或 WTF_CSRF_SECRET_KEY 也没有什么奇怪的地方(它们已正确设置)。

有人有什么想法吗?

【问题讨论】:

  • 我最后不得不清除我的 cookie。对于阅读本文的其他人来说,这可能是一个快速的解决方案。

标签: python session flask wtforms flask-wtforms


【解决方案1】:

无需经历上述漫长的过程,只需将以下 jinja 代码 {{ form.csrf_token }} 添加到表单的 html 端,这应该可以解决“CSRF 令牌丢失”错误。所以在 HTML 方面,它看起来像这样:

<form action="{{url_for('signup')}}" method="POST">
    {{ form.csrf_token }}

<fieldset class="name">
    {{ form.name.label}}
    {{ form.name(placeholder='John Doe')}}
</fieldset>
.
.
.
{{ form.submit()}}

【讨论】:

  • “上面提到的漫长过程”实际上详细涵盖了可能导致此问题的所有可能问题。您的答案已包含在问题中,请参阅“令牌肯定存在于我的表单中,并且已成功发布”
【解决方案2】:

我想通了。它似乎是一个 cookie/会话限制(这可能超出了 Flask 的控制),并且在达到限制时会静默丢弃会话变量(这看起来更像是一个错误)。

这是一个例子:

templates/hello.html

<p>{{ message|safe }}</p>
<form name="loginform" method="POST">
  {{ form.hidden_tag() }}
  {{ form.submit_button() }}
</form>

myapp.py

from flask import Flask, make_response, render_template, session
from flask_restful import Resource, Api
from flask_wtf import csrf, Form
from wtforms import SubmitField

app = Flask(__name__)
app.secret_key = '5accdb11b2c10a78d7c92c5fa102ea77fcd50c2058b00f6e'
api = Api(app)

num_elements_to_generate = 500

class HelloForm(Form):
    submit_button = SubmitField('Submit This Form')

class Hello(Resource):
    def check_session(self):
        if session.get('big'):
            message = "session['big'] contains {} elements<br>".format(len(session['big']))
        else:
            message = "There is no session['big'] set<br>"
        message += "session['secret'] is {}<br>".format(session.get('secret'))
        message += "session['csrf_token'] is {}<br>".format(session.get('csrf_token'))
        return message

    def get(self):
        myform = HelloForm()
        session['big'] = list(range(num_elements_to_generate))
        session['secret'] = "A secret phrase!"
        csrf.generate_csrf()
        message = self.check_session()
        return make_response(render_template("hello.html", message=message, form=myform), 200, {'Content-Type': 'text/html'})

    def post(self):
        csrf.generate_csrf()
        message = self.check_session()
        return make_response("<p>This is the POST result page</p>" + message, 200, {'Content-Type': 'text/html'})

api.add_resource(Hello, '/')

if __name__ == '__main__':
    app.run(debug=True)

num_elements_to_generate 设置为 500 运行此命令,您将得到如下结果:

session['big'] contains 500 elements
session['secret'] is 'A secret phrase!'
session['csrf_token'] is a6acb57eb6e62876a9b1e808aa1302d40b44b945

和“提交此表单”按钮。点击按钮,你会得到:

This is the POST result page
session['big'] contains 500 elements
session['secret'] is 'A secret phrase!'
session['csrf_token'] is a6acb57eb6e62876a9b1e808aa1302d40b44b945

一切顺利。但是现在将 num_elements_to_generate 更改为 3000,清除 cookie,重新运行应用程序并访问页面。你会得到类似的东西:

session['big'] contains 3000 elements
session['secret'] is 'A secret phrase!'
session['csrf_token'] is 709b239857fd68a4649deb864868897f0dc0a8fd

和“提交此表单”按钮。点击按钮,这次你会得到:

This is the POST result page
There is no session['big'] set
session['secret'] is 'None'
session['csrf_token'] is 13553dce0fbe938cc958a3653b85f98722525465

存储在会话变量中的 3,000 位太多了,因此会话变量在请求之间不会持续存在。有趣的是,它们确实存在于第一页的会话中(无论您生成多少元素),但它们将无法生存到下一个请求。而 Flask-WTF,由于在发布表单时在会话中看不到 csrf_token,因此会生成一个新的。如果这是一个表单验证步骤,CSRF 验证将失败。

这似乎是一个已知的 Flask(或 Werkzeug)错误,with a pull request here。我不确定为什么 Flask 没有在此处生成警告 - 除非它在技术上不可行,否则当 cookie 太大时它会默默地保持会话变量失败,这是一个意外且令人不快的惊喜。

【讨论】:

    猜你喜欢
    • 2018-03-11
    • 2020-02-10
    • 2017-09-27
    • 2018-07-16
    • 2016-11-08
    • 2017-05-24
    • 2022-12-21
    • 2019-06-01
    • 1970-01-01
    相关资源
    最近更新 更多