Flaskおよび Flask-Login 拡張機能を使用して、Flaskアプリにユーザー認証を実装しようとしています。目標はデータベースからユーザーアカウント情報を取得してからユーザーにログインしますが、行き詰まります。ただし、Flask-Loginの動作の特定の部分に絞り込みました。
Flask-Loginのドキュメント によると、user_loaderの「コールバック」関数を作成する必要があります。この関数の実際の目的と実装により、私は数日間混乱しました。
User_loaderコールバックを提供する必要があります。このコールバックは、セッションに格納されているユーザーIDからユーザーオブジェクトをリロードするために使用されます。ユーザーのUnicodeIDを取得し、対応するユーザーオブジェクトを返す必要があります。例えば:
@login_manager.user_loader def load_user(userid): return User.get(userid)
ここで、ユーザーに名前とパスワードをフォームに入力し、データベースと照合して、ユーザーにログインしてもらいたいとします。データベースのものはうまく機能し、私にとっては問題ありません。
この「コールバック」関数は、ユーザーID#を渡され、Userオブジェクト(データベースからロードしているコンテンツ)を返したいと考えています。しかし、とにかくユーザーIDはすべて同じ場所から取得されるため、チェック/実行するはずの内容が実際にはわかりません。コールバックを機能させるために「並べ替え」ることはできますが、それは厄介/ハックっぽく見え、ブラウザが要求するすべてのリソースでデータベースにヒットします。ページが更新されるたびにfavicon.icoをダウンロードするためにデータベースをチェックしたくないのですが、flask-loginがこれを強制しているようです。
データベースを再度チェックしないと、この関数からUserオブジェクトを返す方法がありません。 Userオブジェクト/クラスはログインのためにflaskルートで作成されるため、コールバックの範囲外です。
私が理解できないのは、毎回データベースにアクセスすることなく、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
ユーザーオブジェクトを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
私が完全には理解していない「コールバック」(データベースからプルした後に作成される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オブジェクトを提供する必要があるため、すべてのデータベースにアクセスする必要がありますログインアクションのが行われます。私はそれをすべて間違っていると確信していますが、その方法がわかりません。
フラスコログインによって提供されるサンプルコード このようにします しかし、これは、データベースのような実際のシナリオのようにではなく、グローバルにハードコードされた辞書からユーザーオブジェクトをプルするためにのみ機能します。ここで、DBをチェックし、ユーザーオブジェクトを作成する必要があります後ユーザーがログイン資格情報を入力します。そして、flask-loginでデータベースを使用することを説明する他のサンプルコードを見つけることができないようです。
ここに何が欠けていますか?
リクエストのたびに、DBからユーザーオブジェクトをロードする必要があります。この要件の最大の理由は、Flask-Loginが認証トークンを毎回チェックして、その継続的な有効性を確認することです。このトークンの計算には、ユーザーオブジェクトに保存されているパラメーターが必要な場合があります。
たとえば、ユーザーが2つの同時セッションを持っているとします。そのうちの1つでは、ユーザーがパスワードを変更します。以降のリクエストでは、アプリケーションを安全に保つために、ユーザーは2番目のセッションからログアウトし、再度ログインする必要があります。ユーザーがコンピューターからログアウトするのを忘れたために2番目のセッションが盗まれた場合を考えてみてください。パスワードを変更して、状況をすぐに修正する必要があります。管理者にユーザーを追い出す機能を与えることもできます。
このような強制ログアウトを行うには、Cookieに保存されている認証トークンが1)パスワードまたは新しいパスワードが設定されるたびに変更される何かに部分的に基づいている必要があります。 2)ビューを実行する前に、DBに格納されているユーザーオブジェクトの最新の既知の属性と照合します。
これが私のコードです。データマッピングオブジェクトとして別の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