flask-login user is set to anonymous after login

2019-07-22 21:07发布

问题:

im new to flask and flask-login and ive been struggling with this for days.

Im trying to log a user in like this:

from creds import auth_username, auth_password, pgsql_dbuser, pgsql_dbpassword, pgsql_db1name
from flask import Flask, render_template, request, Response, redirect, url_for
from flask.ext.bcrypt import Bcrypt
from flask.ext.login import LoginManager, login_required, login_user, current_user, logout_user
import logging
import psycopg2
import uuid
import datetime

app = Flask(__name__)
app.secret_key = str(uuid.uuid4()) # <- required by login_manager.init_app(app)

bcrypt = Bcrypt(app)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'index'


@app.route('/', methods=['GET','POST'])
def index():
        page_name = '/'
        if request.method == 'POST':
                email = request.form['email']
                candidate_password = request.form['password']
                user = finduserindbbyemail(email)
                if user != None:
                        password_hash = checkuserpasswordindb(email)
                        if bcrypt.check_password_hash(password_hash, candidate_password):
                                user_object = User(user)
                                result = login_user(user_object) # <- here for successful login
                                return redirect(url_for('loggedin', user_object=type(user_object), user=user, result=result, current_user=current_user))
                        else:
                                user_object = User(user)
                                error_message = "The password you entered is incorrect"
                                return render_template('index.html', error_message=error_message)
                else:
                        error_message = "The email address you entered does not match any we have in our records"
                        return render_template('index.html', error_message=error_message)

        if request.method == 'GET':
                return render_template('index.html')

I have a User class and a user callback:

class User():

        def __init__(self, user):
                self.user = user

        def is_authenticated(self):
                return True

        def is_active(self):
                return True

        def is_anonymous(self):
                return False

        def get_id(self):
                return unicode(self.user)

@login_manager.user_loader
def load_user(user):
        con = psycopg2.connect(database=pgsql_db1name, user=pgsql_dbuser, password=pgsql_dbpassword, host='localhost')
        uuid = "'"+user+"'"
        cur = con.cursor()
        cur.execute("SELECT uuid FROM users WHERE uuid = "+ uuid)
        uuid = cur.fetchone()
        con.close()
        if uuid != None:
                user = unicode(uuid[0])
                return User.get_id(user)
        else:
                return None

After authentication is successful (apparently?), the user is redirected to a loggedin page which has a @login_required decorator. But instead of loading the loggedin page, the app redirects the user to the login page, telling me the user isnt being logged in? If try to send values to the page and i remove the @login_required decorator so i can see the page, this is what i see in the browser after 'logging in':

current_user.is_authenticated() = False
current_user.is_active() = False
current_user.is_anonymous() = True
current_user.get_id() = None
user_object = <type 'instance'>
user = 2ca1296c-374d-43b4-bb7b-94b8c8fe7e44
login_user = True
current_user = <flask_login.AnonymousUserMixin object at 0x7f2aec80f190> Logout

It looks like my user hasn't been logged and is being treated as anonymous? Can anyone see what I've done wrong? I'm having a lot of trouble understanding how this is supposed to work.

回答1:

So.. I managed to get it to work, but not using the user_loader callback. For whatever reason, my user loader exhibits the same behaviour as this: Flask-login with static user always yielding 401- Unauthorized

Anyway, I used a request_loader callback instead based on this example: http://gouthamanbalaraman.com/blog/minimal-flask-login-example.html

so for a user logging in, which starts here:

if bcrypt.check_password_hash(password_hash, candidate_password):
    user_object = User(user, password_hash)
    result = login_user(user_object) # <- here for successful login
    token = user_object.get_auth_token(user, password_hash) 
    return redirect(url_for('loggedin', token=token))

I create a user object which has the user's id and their password hash. then i log the user in. then i create a time-serialized token of the user id and password hash using itsdangerous. the get_auth_token function is part of the User class. it looks like this:

class User():

    def __init__(self, user, password_hash):
        self.user = user
        self.password = password_hash
        .
        .
        .
    def get_auth_token(self, user, password):
        data = [str(self.user), self.password]
        return serializer.dumps(data, salt=serializer_secret)

you need to create a serializer at the beginning of your code somewhere:

serializer = URLSafeTimedSerializer(serializer_secret)

So after the token is created, pass it to the loggedin view as a URL query parameter. When you try to load a login_required page, like my loggedin page, which is where login_user redirects me to after a successful login, the request_loader callback is executed. it looks like this:

@login_manager.request_loader
def load_user_from_request(request):
    if request.args.get('token'):
        token = request.args.get('token')
        max_age = 1
        try:
            data = serializer.loads(token, salt=serializer_secret, max_age=max_age)
            username = data[0]
            password_hash = data[1]
            found_user = finduserindbbyuuid(username)
            found_password = checkuserpasswordindbbyuuid(username)
            if found_user and found_password == password_hash:
                user_object = User(found_user, password_hash)
                if (user_object.password == password_hash):
                    return user_object
                else:
                    return None
            else:
                return None

        except BadSignature, e:
            pass
    else:
        return None

This is the point where my user_loader was failing. I was logging in successfully, but the user_loader was always returning None and so my user would be deemed as anonymous.

So with the request loader, it checks that the request URL contains a 'token' argument in the query string. if so, it takes its value and using itsdangerous, deserializes the data. you can make the token expire with timed serializers, but there are also non timed ones. after the token is deserialized, take the user and password hash and check in the database if they exist, in exactly the same way that the user_loader was supposed to work.. i imagine? my user_loader didnt work so i was most probably doing it wrong.

anyway if a user and password match in the db, then return the user object and bam, login works.

Im not sure if im doing it the right way, cos pretty much flying by the seat of my pants. i saw examples where people used the token_loader, rather than the request_loader callback function, to load the token, but i couldnt figure out how to set & get the auth token to & from the client. maybe ill figure it out... one day...

if you have the same problem, maybe this might help? or just let me know what you think

cheers



回答2:

Another reason you might not be able to log a user in or current_user is Anonymous after going through your login form: The active=false flag is set on the user in the db. This behavior is confirmed in the docs:

flask_login.login_user(user, remember=False, duration=None, force=False, fresh=True)[source] Logs a user in. You should pass the actual user object to this. If the user’s is_active property is False, they will not be logged in unless force is True.

This will return True if the log in attempt succeeds, and False if it fails (i.e. because the user is inactive).

So, when you call login_user, you can do this: login_user(user, remember=form.remember_me.data, force=True), if you want to allow inactive users to log in.



回答3:

I found this page when searching for help with Flask-Login + Flask-Dance. I was seeing current_user as AnonymousUserMixin in a handler with the @login_required decorator. In my case making sure @app.route is on the line above @login_required fixed the problem. The correct order is in the docs: https://flask-login.readthedocs.io/en/latest/#flask_login.login_required.