Flask WTF CSRF 会话令牌丢失,secret_key 未找到

use*_*330 6 session csrf token flask

我研究了我能找到的关于 Flask WTF 应用程序中“CSRF 会话令牌丢失”的每一篇文章,但到目前为止,我无法在任何有解决方案的解决方案中找到解决方案,或者我错过了它并且没有看到它

\n\n

在本例中,我正在创建一个登录页面,并且在登录表单的 POST/提交时生成错误。

\n\n

在浏览器开发工具中,我可以在表单数据中看到 \xe2\x80\x9ccsrf_token\xe2\x80\x9d 但标题中没有令牌。

\n\n

表单数据来自;

\n\n
 <form method="POST" action="">\n    {{ form.hidden_tag() }}\n    {{ form.csrf_token() }}\n
Run Code Online (Sandbox Code Playgroud)\n\n

在login.html中,但我不知道\xe2\x80\x99是否是预期的结果\xe2\x80\x93,它似乎不起作用。

\n\n

我在想我应该在请求标头中看到 X-CSRFToken 吗?但我不。

\n\n

这是我 \xe2\x80\x9cthink\xe2\x80\x9d 根据我对此错误和配置主题的研究和阅读,我正在正确执行的操作:

\n\n
    \n
  1. 我正在使用 WTF FlaskForm
  2. \n
  3. 我正在使用 WTF CSRFProtect
  4. \n
  5. 我确实设置了 SECRET_KEY (我已经尝试过默认值,专门用于 WTF)
  6. \n
  7. 我不排除 CSRF 的任何观点
  8. \n
  9. 我正在使用 Flask-Login 登录管理器
  10. \n
  11. FireFox 或 Chome 都没有阻止 \xe2\x80\x9csession\xe2\x80\x9d cookie,我可以验证它在两个浏览器中都存在
  12. \n
  13. 在 localhost:5000 上运行,我还尝试了特定的域,例如 local.flask:5000
  14. \n
  15. 我只在会话中存储小字符串(user_id)
  16. \n
\n\n

它应该是一个不同的cookie吗?(例如名为 \xe2\x80\x9ccsrf_token\xe2\x80\x9d 不是 \xe2\x80\x9csession\xe2\x80\x9d 名为 cookie ?)

\n\n

在 WTF csrf.py 中调试时

\n\n

在 validate_csrf() 函数中,我发现;

\n\n
secret_key = _get_config(\n    secret_key, \'WTF_CSRF_SECRET_KEY\', current_app.secret_key,\n    message=\'A secret key is required to use CSRF.\'\n)\n
Run Code Online (Sandbox Code Playgroud)\n\n

返回预期的秘密值:

\n\n
secret_key = {bytes} b\'abc123ced456\'\nfield_name = _get_config(\n    token_key, \'WTF_CSRF_FIELD_NAME\', \'csrf_token\',\n    message=\'A field name is required to use CSRF.\'\n)\n
Run Code Online (Sandbox Code Playgroud)\n\n

回报

\n\n
field_name = {str} \xe2\x80\x98csrf_token\xe2\x80\x99\n
Run Code Online (Sandbox Code Playgroud)\n\n

和 _data 似乎没问题:

\n\n
data = {str} \'IjZiNWY5ZDdiNTZjMTVkM2U0Mzg3MjU1NGMxYzc3Yjg1MTMzYTlhYzEi.XC447w.cmc1INq6u8qVuq0EOL9ARcPwB6k\'\n
Run Code Online (Sandbox Code Playgroud)\n\n

但是它失败,因为 \xe2\x80\x9cfield_name\xe2\x80\x9d 不在会话中

\n\n
if field_name not in session:\n    raise ValidationError(\'The CSRF session token is missing.\')\n
Run Code Online (Sandbox Code Playgroud)\n\n

所以问题是为什么?

\n\n

我还从登录表单方法中检查键/值时出现错误;

\n\n
@app.route("/login", methods=[\'GET\', \'POST\'])\ndef login():\n    test = session[\'secret_key\']\n
Run Code Online (Sandbox Code Playgroud)\n\n

密钥错误:\'secret_key\'

\n\n

app.secret_key 如何到达会话 \xe2\x80\x98secret_key\xe2\x80\x99 ?\n这似乎没有发生。

\n\n

应用程序.py

\n\n
from flask import Flask, render_template, url_for, flash, redirect,  Response, jsonify, abort, session\nfrom flask_session import Session\nfrom flask_wtf.csrf import CSRFProtect\nfrom flask_cors import CORS\n\nfrom flask_login import  LoginManager,UserMixin,current_user,login_required,login_user,logout_user\n\nfrom forms import RegistrationForm, LoginForm, TimecardForm\nfrom employees import employees\n\ncsrf = CSRFProtect()\n\napp = Flask(__name__)\ncsrf.init_app(app)\n\napp.config[\'SECRET_KEY\'] = os.getenv(\'SECRET_KEY\') or \\\n    \'abc123ced456\'\n\napp.config[\'SESSION_TYPE\'] = \'memcached\'\napp.config[\'WTF_CSRF_ENABLED\'] = True\napp.config[\'WTF_CSRF_SECRET_KEY\'] = os.getenv(\'SECRET_KEY\') or \\\n    \'abc123ced456\'\napp.config[\'SESSION_COOKIE_SECURE\'] =  True\napp.config[\'REMEMBER_COOKIE_SECURE\'] =  True\n\nCORS(app)\nsess = Session()\nsess.init_app(app)\n\n\nlogin_manager = LoginManager()\nlogin_manager.init_app(app)\nlogin_manager.session_protection = "strong"\nlogin_manager.login_view = \'login\'\n\n\n@login_manager.user_loader\ndef load_user(userid):\n    result = None\n    emp_collection = employees.oEmployeeCollection()\n    emp_collection.getAllEmployees(None, None)\n    result = emp_collection.getEmployee(userid)\n\n    return result\n\n@app.route("/login", methods=[\'GET\', \'POST\'])\ndef login():\n    form = LoginForm()\n\n    if form.validate_on_submit():\n        emp_collection = employees.oEmployeeCollection()\n        emp_collection.getAllEmployees(None, None)\n        current_user = emp_collection.getEmployee(form.user_init.data.upper())\n\n        if current_user is not None:\n            if current_user.password == form.password.data:\n                login_user(current_user, remember=True)\n                sess[\'current_user\'] = current_user.toJSON()\n\n                flash(\'You have been logged in!\', \'success\')\n\n                #next = flask.request.args.get(\'next\')\n                ## is_safe_url should check if the url is safe for redirects.\n                #if not is_safe_url(next):\n                #    return flask.abort(400)\n                #return flask.redirect(next or flask.url_for(\'index\'))\n\n                return redirect(url_for(\'home\'))\n            else:\n                flash(\'Login Unsuccessful. Please check username and password\', \'danger\')\n\n        else:\n            flash(\'Login Unsuccessful. Please check username and password\', \'danger\')\n\n    flash(form.errors)\n    return render_template(\'login.html\', title=\'Login\', form=form)\n\n\n@app.before_first_request\ndef execute_this():\n    # emp_collection.getAllEmployees(None, None)\n    test = None\n\nif __name__ == \'__main__\':\n    app.run(host=\'flask.local\', port=5000, debug=False)\n
Run Code Online (Sandbox Code Playgroud)\n\n

登录.html

\n\n
{% extends "template.html" %}\n{% block content %}\n    <div class="content-section">\n        <form method="POST" action="">\n            {{ form.hidden_tag() }}\n            {{ form.csrf_token() }}\n\n            <fieldset class="form-group">\n                <legend class="border-bottom mb-4">Log In</legend>\n\n                <div class="form-group">\n                    {{ form.user_init.label(class="form-control-label")}}\n                    {% if form.user_init.errors %}\n                        {{ form.user_init(class="form-control form-control-lg is-invalid") }}\n                        <div class="invalid-feedback">\n                            {% for error in form.user_init.errors %}\n                                <span>{{ error }}</span>\n                            {% endfor %}\n                        </div>\n                    {% else %}\n                        {{ form.user_init(class="form-control form-control-lg") }}\n                    {% endif %}\n                </div>\n                <div class="form-group">\n                    {{ form.password.label(class="form-control-label") }}\n                    {% if form.password.errors %}\n                        {{ form.password(class="form-control form-control-lg is-invalid") }}\n                            <div class="invalid-feedback">\n                            {% for error in form.password.errors %}\n                                <span>{{ error }}</span>\n                            {% endfor %}\n                        </div>\n                    {% else %}\n                        {{ form.password(class="form-control form-control-lg") }}\n                    {% endif %}\n                </div>\n                <div class="form-check">\n                    {{ form.remember(class="form-check-input") }}\n                    {{ form.remember.label(class="form-check-label") }}\n                </div>\n            </fieldset>\n            <div class="form-group">\n                {{ form.submit(class="btn btn-outline-info") }}\n            </div>\n            <small class="text-muted ml-2">\n                <a href="#">Forgot Password?</a>\n            </small>\n        </form>\n    </div>\n    <div class="border-top pt-3">\n        <small class="text-muted">\n            Need An Account? <a class="ml-2" href="{{ url_for(\'register\') }}">Sign Up Now</a>\n        </small>\n    </div>\n{% endblock content %}\n
Run Code Online (Sandbox Code Playgroud)\n\n

表格.py

\n\n
from flask_wtf import FlaskForm\nfrom wtforms import StringField, PasswordField, SubmitField,      BooleanField, DateField, DecimalField\nfrom wtforms.validators import DataRequired, Length, Email, EqualTo\n\nclass LoginForm(FlaskForm):\n    user_init = StringField(\'User\',  validators=[DataRequired()])\n    password = PasswordField(\'Password\', validators=[DataRequired()])\n    remember = BooleanField(\'Remember Me\')\n    submit = SubmitField(\'Login\')\n
Run Code Online (Sandbox Code Playgroud)\n\n

请求结果

\n\n

回复

\n\n
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n<title>400 Bad Request</title>\n<h1>Bad Request</h1>\n<p>The CSRF session token is missing.</p>\n
Run Code Online (Sandbox Code Playgroud)\n\n

会话cookie

\n\n
Content-Type \xe2\x86\x92text/html\nContent-Length \xe2\x86\x92142\nAccess-Control-Allow-Origin \xe2\x86\x92*\nSet-Cookie \xe2\x86\x92session=ad0a88f2-4048-4a3b-9934-c2cd5957e9ff; Expires=Sun, 03-Feb-2019 14:55:27 GMT; HttpOnly; Path=/\nServer \xe2\x86\x92Werkzeug/0.14.1 Python/3.7.1\nDate \xe2\x86\x92Thu, 03 Jan 2019 14:55:27 GMT\n
Run Code Online (Sandbox Code Playgroud)\n\n

请求一般

\n\n
Request URL: http://localhost:5000/login\nRequest Method: POST\nStatus Code: 400 BAD REQUEST\nRemote Address: 127.0.0.1:5000\nReferrer Policy: no-referrer-when-downgrade\n
Run Code Online (Sandbox Code Playgroud)\n\n

响应头

\n\n
Access-Control-Allow-Origin: http://localhost:5000\nContent-Length: 150\nContent-Type: text/html\nDate: Thu, 03 Jan 2019 14:47:18 GMT\nServer: Werkzeug/0.14.1 Python/3.7.1\nSet-Cookie: session=62e6139c-332b-4811-ad3a-de5c29c878aa; Expires=Sun, 03-Feb-2019 14:47:18 GMT; HttpOnly; Path=/\nVary: Origin\n
Run Code Online (Sandbox Code Playgroud)\n\n

请求标头

\n\n
POST /login HTTP/1.1\nHost: localhost:5000\nConnection: keep-alive\nContent-Length: 258\nCache-Control: max-age=0\nUpgrade-Insecure-Requests: 1\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36\nOrigin: http://localhost:5000\nContent-Type: application/x-www-form-urlencoded\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\nReferer: http://localhost:5000/login\nAccept-Encoding: gzip, deflate, br\nAccept-Language: en-US,en;q=0.9\n\nCookie: Webstorm-655f3561=d5da8892-b9fc-4680-8fe8-17baf5fd6f8d;session=62e6139c-332b-4811-ad3a-de5c29c878aa\n
Run Code Online (Sandbox Code Playgroud)\n\n

表格数据

\n\n
csrf_token=ImI5ZDlkYjZmNjkxMDZlZDczZjdlY2VjMTM2NTQzOWZlMDBkYTY1ZWMi.XC4gZQ.DVyKZ07nrQN6WZn0jmoHyKrf_YI&\n        csrf_token=ImI5ZDlkYjZmNjkxMDZlZDczZjdlY2VjMTM2NTQzOWZlMDBkYTY1ZWMi.XC4gZQ.DVyKZ07nrQN6WZn0jmoHyKrf_YI&user_init=ABC&password=changeme&remember=y&submit=Login\n
Run Code Online (Sandbox Code Playgroud)\n

小智 6

我昨天遇到了“CSRF 令牌丢失”问题,幸运的是,我找到了问题的原因。我已经按照此指令使用同步工作配置在 Gunicorn + Nginx 上部署了 Flask 应用程序,这就是问题所在。Flask 无法与 Gunicorn 的同步工作线程一起使用,因此转向线程已经解决了我的问题。

Gunicorn --workers 1 --threads 3 -b 0.0.0.0:5000 wsgi:app


Dav*_*ith 2

{{ form.hidden_tag() }}应该扩展到类似的东西

<input id="csrf_token" name="csrf_token" type="hidden" value="... long string ...">
Run Code Online (Sandbox Code Playgroud)

如果您没有看到这一点,请仔细检查您如何设置应用程序的配置部分。除了 之外SECRET_KEY,您还设置任何选项吗WTF_

您可能想要删除{{ form.csrf_token() }}

X-涉及标头。(我快速检查了我的一个应用程序,以防我忘记了什么。)