32

我正在尝试使用 Flask 和Flask-Login扩展在 Flask 应用程序中实现用户身份验证。目标是从数据库中提取用户帐户信息,然后登录用户,但我卡住了;但是,我已将其范围缩小到 Flask-Login 行为的特定部分。

根据Flask-Login 文档,我需要创建一个 user_loader“回调”函数。这个函数的实际目的和实现让我困惑了几天:

您将需要提供一个 user_loader 回调。此回调用于从会话中存储的用户 ID 重新加载用户对象。它应该获取用户的 Unicode ID,并返回相应的用户对象。例如:

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

现在,假设我希望用户在表单中输入名称和密码,检查数据库,然后登录用户。数据库的东西工作正常,对我来说没问题。

这个“回调”函数想要传递一个用户 ID #,并返回用户对象(我从数据库加载的内容)。但我并没有真正得到它应该检查/做的事情,因为无论如何用户ID都是从同一个地方提取的。我可以“有点”让回调工作,但它看起来很混乱/hackish,它会使用浏览器请求的每一个资源来访问数据库。我真的不想检查我的数据库以便在每次刷新页面时下载 favicon.ico,但 flask-login 似乎是在强迫这样做。

如果我不再次检查数据库,那么我无法从此函数返回用户对象。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

我为将用户对象返回给 Flask-Login 的 user_loader 回调函数所做的函数:

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

我不完全理解的“回调”(必须返回用户对象,该对象是在从数据库中提取后创建的):

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

我的代码“有点”有效,我可以登录和注销,但正如我所说,它必须为所有内容访问数据库,因为我必须在与其他名称空间/范围不同的回调函数中提供用户对象的登录操作发生。我很确定我做错了,但我不知道怎么做。

flask-login 提供的示例代码就是这样做的,但这只是因为它从全局硬编码字典中提取用户对象,而不是像数据库这样的真实场景中,必须检查数据库和在用户输入其登录凭据创建的用户对象。而且我似乎找不到任何其他示例代码来说明使用带有烧瓶登录的数据库。

这里缺少什么?

4

3 回答 3

31

您需要在每次请求时从数据库加载用户对象。该要求的最强有力的原因是 Flask-Login 每次都会检查身份验证令牌以确保其持续有效性。该令牌的计算可能需要存储在用户对象上的参数。

例如,假设一个用户有两个并发会话。在其中之一中,用户更改了他们的密码。在随后的请求中,用户必须退出第二个会话并强制重新登录,以确保您的应用程序安全。想想第二个会话因为您的用户忘记注销计算机而被盗的情况——您希望更改密码以立即解决这种情况。您可能还希望让您的管理员能够将用户踢出。

为了发生这种强制注销,存储在 cookie 中的身份验证令牌必须 1) 部分基于密码或每次设置新密码时都会更改的其他内容;2) 在运行任何视图之前检查用户对象的最新已知属性 - 这些属性存储在数据库中。

于 2012-05-22T05:38:03.387 回答
2

我确实和你一样担心 Edmond:每次需要知道用户的角色或名称时都访问数据库是疯狂的。最好的方法是将您的用户对象存储在会话甚至应用程序范围的缓存中,每隔几分钟从数据库中更新一次。我个人为此使用 Redis(这样网站可以在使用单个缓存入口点时由多个线程/进程运行)。我只是确保 Redis 配置了密码和非默认端口,并且任何机密数据(如用户哈希等)都以加密形式存储在那里。缓存可以由在指定时间间隔运行的单独脚本填充,或者可以在 Flask 中生成单独的线程。注意:Flask-Session 也可以配置为使用(相同的)redis 实例来存储会话数据,在这种情况下,将需要具有 'bytes' 数据类型的实例,

于 2020-04-27T13:38:32.823 回答
1

这是我的代码,另一个User作为数据映射对象提供的query_pwd_md5方法。

用户登录:

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

用户等级:

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
于 2018-12-13T03:06:21.557 回答