Подтвердить что ты не робот

Как реализовать обратный вызов user_loader в Flask-Login

Я пытаюсь использовать Flask и расширение Flask-Login для реализации аутентификации пользователей в приложении Flask. Цель состоит в том, чтобы вытащить данные учетной записи пользователя из базы данных, а затем войти в систему пользователя, но я застрял; тем не менее, я сузил его до определенной части поведения Flask-Login.

В соответствии с Документация флэшка-входа, мне нужно создать функцию обратного вызова user_loader. Фактическая цель и реализация этой функции заставили меня запутаться в течение нескольких дней:

Вам нужно будет предоставить обратный вызов user_loader. Этот обратный вызов используется для перезагрузки пользовательского объекта из идентификатора пользователя, сохраненного в сеансе. Это должен принимать идентификатор Unicode пользователя и возвращать соответствующий пользовательский объект. Например:

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

Теперь, скажем, я хочу, чтобы пользователь вводил имя и пароль в форму, проверял базу данных и регистрировал пользователя. Материал базы данных отлично работает и для меня не проблема.

Эта функция обратного вызова хочет передать идентификатор пользователя # и вернуть объект User (содержимое которого я загружаю из базы данных). Но я действительно не понимаю, что он должен проверять/делать, поскольку идентификаторы пользователей все равно вытаскиваются из одного и того же места. Я могу "сортировать" "заставить обратный вызов работать, но он кажется беспорядочным/хакерским, и он попадает в базу данных с каждым единственным ресурсом, который запрашивает браузер. Я действительно не хочу проверять свою базу данных, чтобы загрузить favicon.ico с каждой обновленной страницей, но флажок-логин, похоже, заставляет это.

Если я не проверю базу данных снова, то у меня нет способа вернуть объект User из этой функции. Объект/класс пользователя создается в маршруте колбы для входа в систему и, следовательно, выходит за пределы области обратного вызова.

Я не могу понять, как передать объект User в эту функцию обратного вызова, без необходимости удалять базу данных каждый раз. Или, иначе выясните, как это сделать более эффективным образом. Я должен упустить что-то фундаментальное, но я смотрел на него уже несколько дней, бросая на него всевозможные функции и методы, и ничего не получается.

Вот соответствующие фрагменты из моего тестового кода. Класс пользователя:

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

Функция, которую я сделал, чтобы вернуть объект пользователя функции обратного вызова user_loader 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

"Обратный вызов", который я не совсем понимаю (должен вернуть объект User, который создается после вытаскивания из базы данных):

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

Маршрут входа:

@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")

Мой код "kinda" работает, я могу войти и выйти, но, как я уже сказал, он должен попасть в базу данных абсолютно для всех, потому что я должен предоставить объект User функции обратного вызова в другом пространстве/области где выполняется остальная часть действия входа. Я почти уверен, что все делаю неправильно, но я не могу понять, как это сделать.

Пример кода, предоставленного флажком-login делает это таким образом, но это работает только потому, что он вытягивает объекты User из глобального жесткого закодированный словарь, а не как в реальном сценарии, например в базе данных, где должна быть проверена БД, и пользовательские объекты, созданные после того, как пользователь вводит свои учетные данные. И я не могу найти какой-либо другой пример кода, который иллюстрирует использование базы данных с флэшем-логином.

Что здесь отсутствует?

4b9b3361

Ответ 1

Вам нужно будет загрузить пользовательский объект из БД при каждом запросе. Самой сильной причиной этого требования является то, что Flask-Login будет проверять токен аутентификации каждый раз, чтобы гарантировать его сохранность. Для вычисления этого токена могут потребоваться параметры, хранящиеся на объекте пользователя.

Например, предположим, что пользователь имеет два параллельных сеанса. В одном из них пользователь меняет свой пароль. В последующих запросах пользователь должен быть выведен из второго сеанса и вынужден заново заходить в систему, чтобы ваше приложение было безопасным. Подумайте о том, когда второй сеанс украден, потому что ваш пользователь забыл выйти из компьютера - вы хотите изменить пароль, чтобы немедленно исправить ситуацию. Вы также можете дать своим администраторам возможность выгнать пользователя.

Для такого принудительного выхода из системы токен аутентификации, хранящийся в файле cookie, должен: 1) основываться частично на пароле или что-то еще, которое изменяется каждый раз, когда устанавливается новый пароль; 2) перед тем, как запускать какое-либо представление, проверять последние известные атрибуты пользовательского объекта, которые хранятся в БД.

Ответ 2

Вот мой код, другой User качестве объектов отображения данных предоставляет метод query_pwd_md5.

Логин пользователя:

uname, request_pwd = extract_uname_pwd(request)

try:
    user.check_pwd(request_pwd, User.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)

Класс пользователя:

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