我研究了我能找到的关于 Flask WTF 应用程序中“CSRF 会话令牌丢失”的每一篇文章,但到目前为止,我无法在任何有解决方案的解决方案中找到解决方案或者我错过了它并且没有看到它。
在本例中,我正在创建一个登录页面,并且在登录表单的 POST/提交时生成错误。
在浏览器开发工具中,我可以在表单数据中看到“csrf_token”,但标题中没有令牌。
表单数据来自;
<form method="POST" action="">
{{ form.hidden_tag() }}
{{ form.csrf_token() }}
在login.html中,但我不知道这是否是预期的结果——它似乎不起作用。
我想我应该在请求标头中看到 X-CSRFToken ?但我不知道。
它应该是一个不同的cookie吗? (例如,名为“csrf_token”而不是名为 cookie 的“会话”?)
在 validate_csrf() 函数中,我发现;
secret_key = _get_config(
secret_key, 'WTF_CSRF_SECRET_KEY', current_app.secret_key,
message='A secret key is required to use CSRF.'
)
返回预期的秘密值:
secret_key = {bytes} b'abc123ced456'
field_name = _get_config(
token_key, 'WTF_CSRF_FIELD_NAME', 'csrf_token',
message='A field name is required to use CSRF.'
)
退货
field_name = {str} ‘csrf_token’
并且_data看起来没问题:
data = {str} 'IjZiNWY5ZDdiNTZjMTVkM2U0Mzg3MjU1NGMxYzc3Yjg1MTMzYTlhYzEi.XC447w.cmc1INq6u8qVuq0EOL9ARcPwB6k'
但是它失败了,因为“field_name”不在会话中
if field_name not in session:
raise ValidationError('The CSRF session token is missing.')
所以问题是为什么?
我还从登录表单方法中检查键/值时出现错误;
@app.route("/login", methods=['GET', 'POST'])
def login():
test = session['secret_key']
密钥错误:“秘密密钥”
app.secret_key 如何获取会话‘secret_key’? 这似乎没有发生。
from flask import Flask, render_template, url_for, flash, redirect, Response, jsonify, abort, session
from flask_session import Session
from flask_wtf.csrf import CSRFProtect
from flask_cors import CORS
from flask_login import LoginManager,UserMixin,current_user,login_required,login_user,logout_user
from forms import RegistrationForm, LoginForm, TimecardForm
from employees import employees
csrf = CSRFProtect()
app = Flask(__name__)
csrf.init_app(app)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY') or \
'abc123ced456'
app.config['SESSION_TYPE'] = 'memcached'
app.config['WTF_CSRF_ENABLED'] = True
app.config['WTF_CSRF_SECRET_KEY'] = os.getenv('SECRET_KEY') or \
'abc123ced456'
app.config['SESSION_COOKIE_SECURE'] = True
app.config['REMEMBER_COOKIE_SECURE'] = True
CORS(app)
sess = Session()
sess.init_app(app)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.session_protection = "strong"
login_manager.login_view = 'login'
@login_manager.user_loader
def load_user(userid):
result = None
emp_collection = employees.oEmployeeCollection()
emp_collection.getAllEmployees(None, None)
result = emp_collection.getEmployee(userid)
return result
@app.route("/login", methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
emp_collection = employees.oEmployeeCollection()
emp_collection.getAllEmployees(None, None)
current_user = emp_collection.getEmployee(form.user_init.data.upper())
if current_user is not None:
if current_user.password == form.password.data:
login_user(current_user, remember=True)
sess['current_user'] = current_user.toJSON()
flash('You have been logged in!', 'success')
#next = flask.request.args.get('next')
## is_safe_url should check if the url is safe for redirects.
#if not is_safe_url(next):
# return flask.abort(400)
#return flask.redirect(next or flask.url_for('index'))
return redirect(url_for('home'))
else:
flash('Login Unsuccessful. Please check username and password', 'danger')
else:
flash('Login Unsuccessful. Please check username and password', 'danger')
flash(form.errors)
return render_template('login.html', title='Login', form=form)
@app.before_first_request
def execute_this():
# emp_collection.getAllEmployees(None, None)
test = None
if __name__ == '__main__':
app.run(host='flask.local', port=5000, debug=False)
{% extends "template.html" %}
{% block content %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }}
{{ form.csrf_token() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Log In</legend>
<div class="form-group">
{{ form.user_init.label(class="form-control-label")}}
{% if form.user_init.errors %}
{{ form.user_init(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.user_init.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.user_init(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.password.label(class="form-control-label") }}
{% if form.password.errors %}
{{ form.password(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.password.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.password(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-check">
{{ form.remember(class="form-check-input") }}
{{ form.remember.label(class="form-check-label") }}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
<small class="text-muted ml-2">
<a href="#">Forgot Password?</a>
</small>
</form>
</div>
<div class="border-top pt-3">
<small class="text-muted">
Need An Account? <a class="ml-2" href="{{ url_for('register') }}">Sign Up Now</a>
</small>
</div>
{% endblock content %}
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField, DateField, DecimalField
from wtforms.validators import DataRequired, Length, Email, EqualTo
class LoginForm(FlaskForm):
user_init = StringField('User', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
remember = BooleanField('Remember Me')
submit = SubmitField('Login')
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>400 Bad Request</title>
<h1>Bad Request</h1>
<p>The CSRF session token is missing.</p>
Content-Type →text/html
Content-Length →142
Access-Control-Allow-Origin →*
Set-Cookie →session=ad0a88f2-4048-4a3b-9934-c2cd5957e9ff; Expires=Sun, 03-Feb-2019 14:55:27 GMT; HttpOnly; Path=/
Server →Werkzeug/0.14.1 Python/3.7.1
Date →Thu, 03 Jan 2019 14:55:27 GMT
Request URL: http://localhost:5000/login
Request Method: POST
Status Code: 400 BAD REQUEST
Remote Address: 127.0.0.1:5000
Referrer Policy: no-referrer-when-downgrade
Access-Control-Allow-Origin: http://localhost:5000
Content-Length: 150
Content-Type: text/html
Date: Thu, 03 Jan 2019 14:47:18 GMT
Server: Werkzeug/0.14.1 Python/3.7.1
Set-Cookie: session=62e6139c-332b-4811-ad3a-de5c29c878aa; Expires=Sun, 03-Feb-2019 14:47:18 GMT; HttpOnly; Path=/
Vary: Origin
POST /login HTTP/1.1
Host: localhost:5000
Connection: keep-alive
Content-Length: 258
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Origin: http://localhost:5000
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://localhost:5000/login
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: Webstorm-655f3561=d5da8892-b9fc-4680-8fe8-17baf5fd6f8d;session=62e6139c-332b-4811-ad3a-de5c29c878aa
csrf_token=ImI5ZDlkYjZmNjkxMDZlZDczZjdlY2VjMTM2NTQzOWZlMDBkYTY1ZWMi.XC4gZQ.DVyKZ07nrQN6WZn0jmoHyKrf_YI&
csrf_token=ImI5ZDlkYjZmNjkxMDZlZDczZjdlY2VjMTM2NTQzOWZlMDBkYTY1ZWMi.XC4gZQ.DVyKZ07nrQN6WZn0jmoHyKrf_YI&user_init=ABC&password=changeme&remember=y&submit=Login
我昨天遇到了“CSRF 令牌丢失”问题,幸运的是,我找到了问题的原因。我已经使用同步工作配置在 Gunicorn + Nginx 上部署了 Flask 应用程序,然后是此指令,这就是问题所在。 Flask 无法与 Gunicorn 的同步工作线程一起使用,因此转向线程已经解决了我的问题。
gunicorn --workers 1 --threads 3 -b 0.0.0.0:5000 wsgi:app
{{ form.hidden_tag() }}
应该扩展成类似
<input id="csrf_token" name="csrf_token" type="hidden" value="... long string ...">
如果您没有看到这一点,请仔细检查您如何设置应用程序的配置部分。除了
SECRET_KEY
之外,您是否设置了任何 WTF_
选项?
您可能想要删除
{{ form.csrf_token() }}
不涉及
X-
标头。 (我快速检查了我的一个应用程序,以防我忘记了什么。)
我就把它留在这里。
我在实际的软件包版本中遇到了类似的问题(Flask==2.0.1,Flask-WTF==0.15.1,Flask-Login==0.5.0)。
分析 Flask-WTF 的代码,我注意到,如果 csrf 令牌 cookie 已经在全局变量中(flask.g 变量),则它不会在会话中设置。也可能是会话cookie被清除或其他原因。 解决方案是从
g 中删除 csrf 令牌密钥(如果 before_request 部分的 session 变量中不存在):
from flask import g, session
from app import app
@app.before_request
def fix_missing_csrf_token():
if app.config['WTF_CSRF_FIELD_NAME'] not in session:
if app.config['WTF_CSRF_FIELD_NAME'] in g:
g.pop(app.config['WTF_CSRF_FIELD_NAME'])