Cómo implementar la callback user_loader en Flask-Login

Estoy intentando usar Flask y la extensión de inicio de sesión de Flask para implementar la autenticación de usuario en una aplicación de Flask. El objective es extraer la información de la cuenta de usuario de una base de datos y luego iniciar sesión en un usuario, pero me estoy atascando; sin embargo, lo he reducido a una parte particular del comportamiento de inicio de sesión de Flask.

De acuerdo con la documentación de inicio de sesión de Flask , necesito crear una función de “callback” de user_loader. El propósito real y la implementación de esta función me han confundido durante algunos días:

Tendrá que proporcionar una callback user_loader. Esta callback se utiliza para volver a cargar el objeto de usuario desde la ID de usuario almacenada en la sesión. Debe tomar el ID de Unicode de un usuario y devolver el objeto de usuario correspondiente. Por ejemplo:

@login_manager.user_loader def load_user(userid): return User.get(userid) 

Ahora, digamos que quiero que el usuario ingrese un nombre y contraseña en un formulario, verifique en una base de datos e inicie sesión en el usuario. La base de datos funciona bien y no es un problema para mí.

Esta función de “callback” quiere que se le pase un número de ID de usuario y que devuelva el objeto Usuario (el contenido del cual estoy cargando desde una base de datos). Pero realmente no entiendo lo que se supone que debe estar comprobando / haciendo, ya que las ID de usuario se extraen del mismo lugar de todos modos. Puedo ‘ordenar’ que la callback funcione, pero parece desordenada / pirateada y llega a la base de datos con cada recurso que solicita el navegador. Realmente no quiero revisar mi base de datos para descargar favicon.ico con cada actualización de la página, pero parece que el inicio de sesión en el matraz está forzando esto.

Si no vuelvo a revisar la base de datos, no tengo forma de devolver un objeto Usuario desde esta función. El objeto / clase Usuario se crea en la ruta del matraz para iniciar sesión y, por lo tanto, queda fuera del scope de la callback.

Lo que no puedo entender es cómo pasar un objeto de usuario a esta función de callback, sin tener que golpear la base de datos cada vez. O, de lo contrario, descubra cómo hacer esto de una manera más efectiva. Debo estar perdiendo algo fundamental, pero lo he estado mirando durante unos días, lanzándole todo tipo de funciones y métodos, y nada está funcionando.

Aquí hay fragmentos relevantes de mi código de prueba. La clase de usuario:

 class UserClass(UserMixin): def __init__(self, name, id, active=True): self.name = name self.id = id self.active = active def is_active(self): return self.active 

La función que hice para devolver el objeto de usuario a la función de callback del cargador de usuario de Flask-Login:

 def check_db(userid): # query database (again), just so we can pass an object to the callback db_check = users_collection.find_one({ 'userid' : userid }) UserObject = UserClass(db_check['username'], userid, active=True) if userObject.id == userid: return UserObject else: return None 

La “callback”, que no entiendo totalmente (debe devolver el objeto Usuario, que se crea después de extraer de la base de datos):

 @login_manager.user_loader def load_user(id): return check_db(id) 

La ruta de inicio de sesión:

 @app.route("/login", methods=["GET", "POST"]) def login(): if request.method == "POST" and "username" in request.form: username = request.form["username"] # check MongoDB for the existence of the entered username db_result = users_collection.find_one({ 'username' : username }) result_id = int(db_result['userid']) # create User object/instance User = UserClass(db_result['username'], result_id, active=True) # if username entered matches database, log user in if username == db_result['username']: # log user in, login_user(User) return url_for("index")) else: flash("Invalid username.") else: flash(u"Invalid login.") return render_template("login.html") 

Mi código ‘algo’ funciona, puedo iniciar sesión y salir, pero como dije, debe golpear la base de datos para absolutamente todo, porque tengo que proporcionar un objeto Usuario a la función de callback en un espacio de nombres / ámbito diferente de donde el rest de la acción de inicio de sesión se lleva a cabo. Estoy bastante seguro de que estoy haciendo todo mal, pero no puedo entender cómo.

El código de ejemplo proporcionado por flask-login lo hace de esta manera , pero esto solo funciona porque está extrayendo los objetos de Usuario de un diccionario global codificado, no como en un escenario del mundo real como una base de datos, donde se debe verificar el DB Objetos de usuario creados después de que el usuario ingrese sus credenciales de inicio de sesión. Y parece que no puedo encontrar ningún otro código de ejemplo que ilustre el uso de una base de datos con flask-login.

¿Qué me falta aquí?

Deberá cargar el objeto de usuario desde la base de datos cada vez que lo solicite. La razón más sólida para este requisito es que Flask-Login comprobará el token de autenticación cada vez que se asegure su validez continua. El cálculo de este token puede requerir parámetros almacenados en el objeto de usuario.

Por ejemplo, supongamos que un usuario tiene dos sesiones concurrentes. En uno de ellos, el usuario cambia su contraseña. En las solicitudes posteriores, el usuario debe cerrar la sesión en la segunda sesión y debe ser forzado a iniciar sesión nuevamente para que su aplicación sea segura. Piense en el caso en el que se roba la segunda sesión porque su usuario olvidó cerrar sesión en una computadora; desea que se cambie la contraseña para solucionar la situación de inmediato. También es posible que desee dar a sus administradores la posibilidad de expulsar a un usuario.

Para que ocurra tal cierre de sesión forzado, el token de autenticación almacenado en una cookie debe 1) basarse en parte en la contraseña o en otra cosa que cambie cada vez que se establezca una nueva contraseña; 2) debe verificarse antes de ejecutar cualquier vista, con los últimos atributos conocidos del objeto de usuario, que se almacenan en la base de datos.

Aquí está mi código, otro User como objeto de asignación de datos proporciona el método de consulta_pwd_md5.

Inicio de sesión de usuario:

 @app.route('/users/login', methods=['POST']) def login(): # check post. uname = request.form.get('user_name') request_pwd = request.form.get('password_md5') user = User() user.id = uname try: user.check_pwd(request_pwd, BacktestUser.query_pwd_md5( uname, DBSessionMaker.get_session() )) if user.is_authenticated: login_user(user) LOGGER.info('User login, username: {}'.format(user.id)) return utils.serialize({'userName': uname}, msg='login success.') LOGGER.info('User login failed, username: {}'.format(user.id)) return abort(401) except (MultipleResultsFound, TypeError): return abort(401) 

Clase de usuario

 class User(UserMixin): """Flask-login user class. """ def __init__(self): self.id = None self._is_authenticated = False self._is_active = True self._is_anoymous = False @property def is_authenticated(self): return self._is_authenticated @is_authenticated.setter def is_authenticated(self, val): self._is_authenticated = val @property def is_active(self): return self._is_active @is_active.setter def is_active(self, val): self._is_active = val @property def is_anoymous(self): return self._is_anoymous @is_anoymous.setter def is_anoymous(self, val): self._is_anoymous = val def check_pwd(self, request_pwd, pwd): """Check user request pwd and update authenticate status. Args: request_pwd: (str) pwd: (unicode) """ if request_pwd: self.is_authenticated = request_pwd == str(pwd) else: self.is_authenticated = False