【问题标题】:Flask-WTF throws error when csrf_enabled is True (SECRET_KEY is set)当 csrf_enabled 为 True 时,Flask-WTF 抛出错误(设置了 SECRET_KEY)
【发布时间】:2020-03-04 02:54:24
【问题描述】:

我遇到了一个关于 Flask-WTF 的 csrf 保护的问题。

当这样实例化表单时:

uform = UserForm(csrf_enabled=False)

一切都按预期工作,并且表单正确显示。然而:

uform = UserForm()

导致类型错误:

Traceback (most recent call last):
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/app.py", line 2463, in __call__
    return self.wsgi_app(environ, start_response)
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/app.py", line 2449, in wsgi_app
    response = self.handle_exception(e)
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/app.py", line 1866, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/app.py", line 1952, in full_dispatch_request
    return self.finalize_request(rv)
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/app.py", line 1969, in finalize_request
    response = self.process_response(response)
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/app.py", line 2268, in process_response
    self.session_interface.save_session(self, ctx.session, response)
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/sessions.py", line 387, in save_session
    samesite=samesite,
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/werkzeug/wrappers/base_response.py", line 481, in set_cookie
    samesite=samesite,
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/werkzeug/http.py", line 1163, in dump_cookie
    buf = [key + b"=" + _cookie_quote(value)]
TypeError: unsupported operand type(s) for +: 'NoneType' and 'bytes'

设置了 SECRET_KEY(WTF_CSRF_SECRET_KEY 也是如此,只是为了确保 Flask-WTF 没有预料到它,即使根据文档它是可选的)。

这是(相关)代码的其余部分:

admin/routes.py

from flask import current_app as app
from .. import db
from ..models import User
from .forms import UserForm

# Set up a Blueprint
admin_bp = Blueprint('admin_bp', __name__, template_folder='templates', static_folder='static')

@admin_bp.route("users/add/", methods=["GET", "POST"])
def add_user():
    #Add user to the DB
    add_user = True

    #uform = UserForm(csrf_enabled=True)
    uform = UserForm()

    if uform.validate_on_submit():
        user = User(username=uform.username.data, email=uform.email.data, admin=uform.admin.data, address=uform.address.data, delivery=uform.delivery.data)
        user.pwhash(uform.password.data)

        try:
            db.session.add(user)
            db.session.commit()
            flash("Benutzer '", user.username, "' erfolgreich hinzugef  gt.")
        except:
            flash("Fehler: Benutzer konnte nicht hinzugef  gt werden. Existiert Benutzer bereits?")

        return redirect(url_for("admin_bp.users"))

    return render_template("/user.html", action="Hinzuf  gen", add_user=add_user, uform=uform, title="Benutzer hinzuf  gen")

管理员/forms.py

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField, BooleanField, SelectField
from wtforms.validators import DataRequired, Email

class UserForm(FlaskForm):
    username = StringField("Username", validators=[DataRequired()])
    email = StringField("Email", validators=[DataRequired(), Email()])
    password = StringField("Neues Passwort")
    address = StringField("Adresse")
    admin = BooleanField("Admin?")
    delivery = SelectField("Delivery", choices=[("1", "test1"), ("2", "test2")])
    submit = SubmitField("OK")

__init__.py:

from flask_sqlalchemy import SQLAlchemy
from flask_static_compress import FlaskStaticCompress
from flask_bootstrap import Bootstrap
from flask_wtf.csrf import CSRFProtect

# Globally accessible libraries
db = SQLAlchemy()
bs = Bootstrap()
csrf = CSRFProtect()

def create_app():
    app = Flask(__name__, instance_relative_config=False, static_folder="static", template_folder="templates")
    from config import Config
    app.config.from_object('config.DevConfig')
    db.init_app(app)
    bs.init_app(app)
    csrf.init_app(app)

    with app.app_context():

        from .models import Image, Gallery, ImageGalleryMap, Delivery, ImageDeliveryMap, Blogpost, User, Log
        db.create_all()

        compress = FlaskStaticCompress(app)

        # Register Blueprints
        from website.admin.routes import admin_bp
        from website.delivery.routes import delivery_bp
        from website.landing.routes import landing_bp
        from website.public.routes import public_bp

        app.register_blueprint(admin_bp, url_prefix='/admin')
        app.register_blueprint(delivery_bp, url_prefix='/delivery')
        app.register_blueprint(landing_bp, url_prefix='/landing')
        app.register_blueprint(public_bp, url_prefix='/')

        return app

有人知道如何解决这个问题吗? 由于(非常明显的)原因,我并不完全热衷于设置 WTF_CSRF_ENABLED=False...

【问题讨论】:

    标签: python flask csrf flask-wtforms wtforms


    【解决方案1】:

    我可以复制您在示例中给出的 Traceback 的唯一方法是 app.secret_key 有效,但 app.session_cookie_name 设置为 None

    请查看我用于测试的以下脚本。运行此脚本并访问http://127.0.0.1:5000 会引发TypeError: unsupported operand type(s) for +: 'NoneType' and 'bytes',这是您在上面发布的确切例外。如果未设置密钥,当我们尝试实例化表单时,我们会得到一个不同的错误,在 flask 尝试服务它引发的请求之前 KeyError: 'A secret key is required to use CSRF.' 。这是脚本:

    from flask import Flask, render_template_string
    from flask_wtf import FlaskForm
    from wtforms import StringField, SubmitField
    
    
    app = Flask(__name__)
    # comment out below and a KeyError is raised, not a TypeError.
    # So the fact that the code raises the TypeError from trying to generate
    # a cookie means that the secret key is set, the form is instantiating and
    # flask is trying to serve the request. So we can rule out this being a
    # problem with secret key not set.
    app.secret_key = "lskdjflksdj"
    # comment out below and the app runs
    app.session_cookie_name = None
    
    
    class MyForm(FlaskForm):
        a_str = StringField()
        submit = SubmitField()
    
    
    template = """
    <form action="" method="POST">
    {{form.a_str()}}
    {{form.submit()}}
    </form>
    """
    
    
    @app.route("/", methods=["GET", "POST"])
    def route():
        # setting csrf_enabled=False makes the problem go away.
        form = MyForm(csrf_enabled=True)
        return render_template_string(template, form=form)
    
    
    if __name__ == "__main__":
        app.run(debug=True)
    

    那么为什么在表单上设置csrf_enabled=False 会阻止错误呢?发生错误时,flask 生成的令牌是 csrf 令牌。当我们在表单上禁用 csrf 检查时,不再需要生成该令牌并且我们不会遇到错误。

    在表单上切换 csrf 保护与是否引发错误有直接关系,这确实使它看起来像是密钥配置的问题,但真正的问题是为什么 key 的值参数传入dump_cookie()NoneType?

    如果您遵循 Traceback,您会看到烧瓶库中的最后一个调用在这里:File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/sessions.py", line 387, in save_session。这里是the source

            response.set_cookie(
                name,
                val,
                expires=expires,
                httponly=httponly,
                domain=domain,
                path=path,
                secure=secure,
                samesite=samesite,
            )
    

    您可以see for yourself 将上面传递给set_cookie 的第一个参数传递给werkzeug.wrappers.base_response.set_cookie 中的key 参数,它本身又传递给werkzeug.http.dump_cookie 中的key 参数(即None 在错误消息中)。

    上面save_session sn-p中name的值是name = self.get_cookie_name(app)函数中的defined earlier,而body of the get_cookie_name method就是return app.session_cookie_name

    app.session_cookie_name 的默认值是 "session",但不知何故,它在您的配置中被 None 覆盖。

    【讨论】:

    • 就是这样!!我已将 SESSION_CCOKIE_NAME 设置为从我的配置中的 .env 文件中读取,但忘记在那里声明变量......现在我觉得很愚蠢。非常感谢您的帮助。
    猜你喜欢
    • 2021-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多