メインコンテンツへスキップ

Flask でユーザー認証の仕組みを入れる(前編)

·2 分
ひとりアドベントカレンダー2024 Python Flask Flask-Login
目次

はじめに
#

「OKAZAKI Shogo のひとりアドベントカレンダー2024」の11日目です。 今回実装するページで、ユーザー認証が必要な箇所があるので、ユーザー認証の仕組みを実装していきます。まずは、Flask-Login の導入から行います。

flask-login を使うための準備
#

Flask-Login 0.7.0 documentationに沿って導入する。

ライブラリを追加する:

1poetry add flask-login

LoginManager クラスをアプリに追加する。今回作成のアプリでは、flask-login の初期化として以下のようにする:

 1import os
 2from flask import Flask
 3from flask_sqlalchemy import SQLAlchemy
 4import flask_login # 追加
 5
 6
 7db = SQLAlchemy()
 8
 9
10def create_app():
11    # appの設定
12    app = Flask(__name__, instance_relative_config=True)
13
14    # configファイルを読み込む
15    config_path = os.path.join("config", "dev.py")
16    app.config.from_pyfile(config_path)
17
18    # DB の設定
19    db.init_app(app)
20
21    # flask-login の初期化(追加)
22    login_manager = flask_login.LoginManager()
23    login_manager.init_app(app)
24
25    # Blueprint の登録
26    from app.views.index import index_bp
27    from app.views.files import files_bp
28    from app.views.regist_file_form import regist_file_form_bp
29    from app.views.regist_file import regist_file_bp
30    from app.views.manage_file import manage_file_bp
31    from app.views.manage_file_list import manage_file_list_bp
32    from app.views.update_file import update_file_bp
33    from app.views.manage_menu import manage_menu_bp
34
35    app.register_blueprint(index_bp)
36    app.register_blueprint(files_bp)
37    app.register_blueprint(regist_file_form_bp)
38    app.register_blueprint(regist_file_bp)
39    app.register_blueprint(manage_file_bp)
40    app.register_blueprint(manage_file_list_bp)
41        app.register_blueprint(update_file_bp)
42    app.register_blueprint(manage_menu_bp)
43
44    return app

まだ、ログインやログアウトに関するエンドポイントは実装してない。

今回は、管理者ユーザーのみ認証を行う。管理者ユーザーの Model は以下の通り:

models/manager_user.py
#

 1from flask_login import UserMixin
 2from werkzeug.security import check_password_hash, generate_password_hash
 3
 4from app import db, login_manager
 5
 6
 7# ManagerUser テーブル
 8class ManagerUser(UserMixin, db.Model):
 9    __tablename__ = "ManagerUser"
10    user_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
11    user_name = db.Column(db.String(255))
12    password = db.Column(db.String(255))
13    service = db.Column(db.String(255))
14    logined_at = db.Column(db.String(255))
15
16    def get_id(self):
17        return str(self.user_id)  # IDをstr型で返す
18
19    def set_password(self, password):
20        self.password = generate_password_hash(password)
21        return self.password
22
23    def check_password(self, password):
24        print(password)
25        print(self.password)
26        return check_password_hash(self.password, password)
27
28
29@login_manager.user_loader
30def load_user(user_id: str) -> ManagerUser:
31    with db.session() as session:
32        return session.get(ManagerUser, int(user_id))

ユーザーモデルについては、以下をのこと留意する:

  • ユーザークラスを簡単に実装するには、flask_login.UserMixin を継承することができる。
    • これを継承することで、flask-login においてユーザーを表すクラスで求められる、以下のプロパティとメソッドが実装される:
      • is_authenticated:ユーザーが認証されている場合に True を返す。
      • is_active:ユーザーがアクティブな場合に True を返す。認証済みであることに加えて、以下の条件も満たしている必要があります:
        • カウントが有効化されている。
        • 停止されていない。
        • アプリケーションが定めるその他の拒否条件に該当していない。
      • is_anonymous:このプロパティは、匿名ユーザーの場合に True を返す。(実際のユーザーは代わりに False を返す。)
      • get_id():ユーザーを一意に識別する str を返す。この値は、user_loader コールバックからユーザーをロードする際に使用される。
        • ID が元々 int 型や他の型の場合、それを str に変換する必要がある。
        • ユーザー ID に当たるプロパティの名前が id ではない場合、独自に実装する必要がある(今回はそれに相当)
  • flask-login が呼び出すコールバック関数 user_loader を定義しておく必要がある(未定義だとエラー)。
    • このコールバック関数は、セッションに保存されているユーザー ID からユーザーオブジェクトを再ロードするために呼び出される。この関数が None を返すとユーザ ID に該当するユーザが存在しないものと判断される。
    • flask-login は @login_manager.user_loader のデコレータがついた関数を呼ぶだけなので、情報がどこにあって、どう読み取るかについてはプログラムする必要がある。
    • 今回の例では、引数に str 型の ID を受け取り、対応するユーザーオブジェクトを返す。
  • Flask-Loginはuser_loader() を呼ぶだけなので、情報がどこにあって、どう読み取るかについては実装する必要がある。
    • @login_manager.user_loader のデコレータをつけた関数がそれに相当。
    • この関数は、引数にID(str)を受け取り、指定したユーザーを返すもの。

パスワードはハッシュ化されたものを格納するようにする。Flaskの依存モジュールとして一緒にインストールされる Werkzeug に、パスワード暗号化に使える関数があるので、これを使う。set_password()check_password() では、ハッシュ化されたパスワードを格納したり、ハッシュ化されたパスワードで比較をするようにしている。

パスワードハッシュを生成するツールを作成する
#

今回、ユーザー登録をする機能は(とりあえずは)設けていないので、パスワードを生成するためのツールを用意する(Werkzeug が入っていれば使える)

tools/generate_password_hash.py

 1from werkzeug.security import generate_password_hash
 2
 3def hash_password():
 4    # ユーザーにパスワードを入力させる
 5    password = input("Enter a password to hash: ")
 6
 7    # パスワードをハッシュ化 (デフォルトのメソッドは 'pbkdf2:sha256')
 8    hashed_password = generate_password_hash(password)
 9
10    # ハッシュ化したパスワードを出力
11    print("Hashed password:", hashed_password)
12
13if __name__ == "__main__":
14    hash_password() 
1$ poetry run python tools/generate_password_hash.py
2The currently activated Python version 3.13.0 is not supported by the project (3.8.12).
3Trying to find and use a compatible version.
4Using python3 (3.8.12)
5Enter a password to hash: python
6Hashed password: scrypt:32768:8:1$JGZGtMoMS8C3Kv4G$571fb0326cd920b0b59f73ac56c1a64dfefa22ecd3f57e06c5966487963aa54700c936b521bf756f5cf72934870b90587d3b3e812249d8019258cc73ee23a4ba

ここで得た値を ManagerUser テーブルの password に書き込んでおく。

実際のログイン・ログアウトの処理とログインが必要なページの作成は明日の記事にて。

参考資料
#