简体   繁体   English

Flask-Login 问题:在身份验证和重定向后 current_user 仍然是 AnonymousUserMixin 类型

[英]Flask-Login Issue: current_user is still of type AnonymousUserMixin after Authentication and Redirect

I have a login page that uses a custom FlaskForm (using WTForms).我有一个使用自定义 FlaskForm(使用 WTForms)的登录页面。 If a user enters the correct credentials, a PostgreSQL database is successfully queried (using flask-sqlalchemy) to see if a user with that name and (hashed) password exists.如果用户输入正确的凭据,则成功查询 PostgreSQL 数据库(使用 flask-sqlalchemy)以查看是否存在具有该名称和(散列)密码的用户。 If there is such a user, login_user(user) is ran, and redirection to the homepage of my site is attempted.如果有这样的用户,则会运行 login_user(user),并尝试重定向到我的网站主页。

I have implemented flask-login (as per the online documentation), but when a user provides valid credentials for logging in, they are redirected back to the login page (as if they had not provided valid credentials).我已经实现了烧瓶登录(根据在线文档),但是当用户提供有效的登录凭据时,他们会被重定向回登录页面(就好像他们没有提供有效的凭据一样)。 I am using Google Chrome.我正在使用谷歌浏览器。

I have determined that after redirecting to the homepage, the current_user is of type AnonymousUserMixin (even though current user in the login function is of type User (which I have defined, inheriting all methods from UserMixin).我已经确定在重定向到主页后,current_user 的类型是 AnonymousUserMixin(即使登录 function 中的当前用户是 User 类型(我已经定义,继承了 UserMixin 的所有方法)。

Here's what I have tried:这是我尝试过的:

  • Ensured my code meets the specifications outlined in the Flask documentation确保我的代码符合 Flask 文档中列出的规范

  • Browsed articles on StackOverflow, Reddit, and various blogs.浏览 StackOverflow、Reddit 和各种博客上的文章。 From those, I have made the following changes to my code:根据这些,我对我的代码进行了以下更改:

  • Inserted the hidden_tag() and csrf_token() fields into my login form (see the final code excerpt)将 hidden_tag() 和 csrf_token() 字段插入到我的登录表单中(请参阅最终代码摘录)

  • Added a secret key to my Flask application向我的 Flask 应用程序添加了密钥

  • Encoded and decoded (with utf8) the id of the current user (see below code, also in the User class definition further below)编码和解码(使用 utf8)当前用户的 id(参见下面的代码,也在下面的用户 class 定义中)

    return str(self.id).encode('utf-8').decode('utf-8')返回 str(self.id).encode('utf-8').decode('utf-8')

As per the flask-login documentation , I have put the following into my file application.py (the file where my flask code is):根据烧瓶登录文档,我已将以下内容放入我的文件 application.py (我的 flask 代码所在的文件):

At the top of the file:在文件的顶部:

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'

A user loader function:一个用户加载器function:

@login_manager.user_loader
def load_user(id):
    id = db.execute("SELECT id FROM users WHERE id=:id", {"id": id})
    return User.get(current_user, id)

A user class (which inherits UserMixin):一个用户 class(继承了 UserMixin):

class User(UserMixin):
    is_active = True
    is_anonymous = False
    def __init__(self, email, name, id, input_password_hash):
        self.id = id
        self.name = name
        self.email = email
        self.password_hash = input_password_hash

    def check_password(self, password, password_hash_byte_literal):
        return bcrypt.checkpw(password.encode('utf8'), password_hash_byte_literal)

    def get_id(self):
        return str(self.id).encode('utf-8').decode('utf-8')

    def get(self, user_id):
        id = db.execute("SELECT id FROM users WHERE id=:user_id", {"user_id": user_id})
        if id:
            name = db.execute("SELECT name FROM users WHERE id=:user_id", {"user_id": user_id})
            email = db.execute("SELECT email FROM users WHERE id=:user_id", {"user_id": user_id})
            password_hash = db.execute("SELECT password_hash FROM users WHERE id=:user_id", {"user_id": user_id})
            user_name_string = ''
            user_email_string = ''
            user_password_hash_string = ''
            for row in name:
                for i in range(len(row)):
                    user_name_string += row[i]
            for row in email:
                for i in range(len(row)):
                    user_email_string += row[i]
            for row in password_hash:
                for i in range(len(row)):
                    user_password_hash_string += row[i]
            return User(user_email_string, user_name_string, user_id, user_password_hash_string)
        else:
            return None

Below is my login route:以下是我的登录路线:

@app.route("/login", methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        email = form.email.data
        password = form.password.data
        user_pw_hash = (db.execute("SELECT password_hash FROM users WHERE email=:email", {"email": email}).fetchone())
        user_id = (db.execute("SELECT id FROM users WHERE email=:email", {"email": email}).fetchone())
        if user_id:
            password_hash_string = ''
            id_string = str(user_id)
            for row in user_pw_hash:
                for i in range(len(row)):
                    password_hash_string += row[i]
            user_id_int = int(id_string[1])
            user = User.get(user, user_id_int)
            password_hash_byte_literal = bytes(password_hash_string, encoding='utf8')
            correct_password = User.check_password(user, password, password_hash_byte_literal)
            if correct_password:
                login_user(user)
                next = url_for("index")
                if not is_safe_url(next, {"http://127.0.0.1:5000"}):
                    return abort(400)
                return redirect(next or url_for("login"))
            else:
                return render_template("login.html", message="Incorrect username or password.", form=form)
        else:
            return render_template("login.html", message="No account with that email address was found.", form=form)

    else:
        return render_template("login.html", form=form)

As per the flask-login documentation, I login the user with the login_user function (see above), and I check to see if the next url (my homepage -- "index") is safe.根据烧瓶登录文档,我使用 login_user function(见上文)登录用户,并检查下一个 url(我的主页 - “索引”)是否安全。 If it is, I proceed to redirect the user to that page.如果是,我继续将用户重定向到该页面。

Also, below is my login form (which includes the hidden_tag() and csrf_token() fields).另外,下面是我的登录表单(其中包括 hidden_tag() 和 csrf_token() 字段)。

<form method="post" action="/login">
    {{ form.hidden_tag() }}
    {{ form.csrf_token() }}
    {{ wtf.form_field(form.email) }}
    {{ wtf.form_field(form.password) }}
    <button type="submit" value="submit">Submit</button><br>
</form>

I realize that this code does not yet properly sanitize inputs before executing PostgreSQL commands.我意识到在执行 PostgreSQL 命令之前,此代码尚未正确清理输入。 I will work on fixing this issue very soon.我会尽快解决这个问题。

Imports:进口:

import os
from flask import flash, Flask, session, redirect, render_template, request, url_for
from flask_bootstrap import Bootstrap
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from flask_session import Session
from is_safe_url import is_safe_url
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from forms import LoginForm, RegistrationForm, ReviewForm   # Custom WTForms I wrote
import bcrypt

Command Line Output when User Submits Form and Redirected to Homepage is Attempted (index)用户提交表单并尝试重定向到主页时的命令行 Output(索引)

127.0.0.1 - - [15/Jun/2020 18:42:35] "GET /login HTTP/1.1" 200 - 127.0.0.1 - - [15/Jun/2020 18:42:35] “GET /login HTTP/1.1”200 -

127.0.0.1 - - [15/Jun/2020 18:42:48] "POST /login HTTP/1.1" 302 - 127.0.0.1 - - [15/Jun/2020 18:42:48] “POST /login HTTP/1.1”302 -

127.0.0.1 - - [15/Jun/2020 18:42:48] "GET / HTTP/1.1" 302 - 127.0.0.1 - - [15/Jun/2020 18:42:48] “GET / HTTP/1.1”302 -

127.0.0.1 - - [15/Jun/2020 18:42:48] "GET /login?next=%2F HTTP/1.1" 200 - 127.0.0.1 - - [15/Jun/2020 18:42:48] “GET /login?next=%2F HTTP/1.1”200 -

I am using Visual Studio code (and its PowerShell) to run and edit this Flask application.我正在使用 Visual Studio 代码(及其 PowerShell)来运行和编辑这个 Flask 应用程序。

Versions:版本:

Windows 10
Google Chrome Version 83.0.4103.106 (Official Build) (64-bit)
bcrypt 3.1.7
email-validator 1.1.1
Python 3.8.2
Flask 1.1.2
Flask-WTF 0.14.3
Flask-SQLAlchemy 2.4.3
Flask-Session 0.3.2
Flask-Login 0.5.0
Flask-Bootstrap
WTForms 2.3.1
SQLAlchemy 1.3.16
mysql-connector-python 8.0.19
mysql-client 0.0.1
Jinja2 2.11.2
itsdangerous 1.1.0
is-safe-url 1.0

Thank you in advance for your help!预先感谢您的帮助!

Update更新

Below is my updated code (with changes made based on others' insightful comments):以下是我更新的代码(根据其他人的有见地的评论进行了更改):

Login Function:登录 Function:

@app.route("/login", methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        email = form.email.data
        password = form.password.data
        user_id = (db.execute("SELECT id FROM users WHERE email=:email", {"email": email}).fetchone())
        if user_id:
            user_pw_hash = (db.execute("SELECT password_hash FROM users WHERE email=:email", {"email": email}).fetchone())
            password_hash_string = user_pw_hash.password_hash
            user = User(None, None, None, False)
            user_id_int = user_id.id
            user = load_user(user_id_int)
            password_hash_byte_literal = bytes(password_hash_string, encoding='utf8')
            correct_password = User.check_password(user, password, password_hash_byte_literal)
            if correct_password:
                login_user(user)
                next = url_for("index")
                if not is_safe_url(next, {"http://127.0.0.1:5000"}):
                    return abort(400)
                else:
                    return redirect(next or url_for("login"))
            else:
                return render_template("login.html", message="Incorrect email or password.", form=form)
        else:
            return render_template("login.html", message="No account with that email address was found.", form=form)
    else:
        return render_template("login.html", form=form)

Login Manager User Loader:登录管理器用户加载器:

@login_manager.user_loader
def load_user(id):
    user_data = db.execute("SELECT * FROM users WHERE id=:id", {"id": id}).fetchone()
    if user_data:
        return User(user_data.email, user_data.name, id, user_data.password_hash)
    else:
        return None

A Get ID function from my User class:从我的用户 class 获取 ID function:

    def get_id(self):
        return self.id

The above two function work correctly, but users are still redirected to the login page after attempting to sign in with valid credentials.上述两个 function 工作正常,但用户在尝试使用有效凭据登录后仍被重定向到登录页面。

Again, thank you all for your help;再次感谢大家的帮助; it is greatly appreciated.非常感谢。

I'm having the same problem as you, when I don't set the remember=True, I can not redirect after using flask.login_user我和你有同样的问题,当我没有设置remember=True时,使用flask.login_user后我无法重定向

Per flask-login docs: https://flask-login.readthedocs.io/en/latest/#flask_login.login_user remember (bool) – Whether to remember the user after their session expires.每个烧瓶登录文档: https://flask-login.readthedocs.io/en/latest/#flask_login.login_user remember (bool) – 是否在 session 过期后记住用户。 Defaults to False.默认为假。

so I think I have some configuration that my session expires right after my initial request, because if I set remember to True所以我想我有一些配置,我的 session 在我的初始请求后立即过期,因为如果我将记住设置为True

so instead of doing:所以不要这样做:

login_user(user)

try尝试

login_user(user=user, remember=True)

Suggestion 2:建议二:

my guess would be to take a double look into your function to get the user from the database def get(self, user_id): , make sure this is returning the user object properly and not None.我的猜测是仔细查看您的 function 以从数据库中获取用户def get(self, user_id): ,确保这是正确返回用户 object 而不是 None。 Also the login_user() from flask.login should return True if the login is successful.如果登录成功,来自 flask.login 的 login_user() 也应该返回 True。 This how I would find and get the user from the db using id:这就是我如何使用 id 从数据库中查找和获取用户的方式:

def find_user_by_id(user_id: int):
row = db.execute('select * from users u where u.id =:user_id',
                 {'user_id': user_id}).fetchone()
if row is None:
    return None
else:
    return User(user_id=row.id, first_name=row.first_name,
                last_name=row.last_name, email=row.email, reviews=None, password_hash=row.password)

You're not implementing the flask-login requirements correctly.您没有正确实施flask-login要求。 Try using the default user_loader callback and seeing if it fixes your issue.尝试使用默认的user_loader回调并查看它是否解决了您的问题。

@login_manager.user_loader
def load_user(id):
    # Whichever method you use to load a user, it needs to be guaranteed unique
    field_values = list(db.execute("SELECT id, name, email, password_hash FROM users WHERE id=:id", {"id": id}))
    
    return User(**dict(field_values))

and in your User model并在您的User model

def get_id(self):
    # this matches what user_loader needs to uniquely load a user
    return self.id

See: https://flask-login.readthedocs.io/en/latest/#how-it-works请参阅: https://flask-login.readthedocs.io/en/latest/#how-it-works

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM