はじめに#
「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 においてユーザーを表すクラスで求められる、以下のプロパティとメソッドが実装される:
- flask-login が呼び出すコールバック関数
user_loader
を定義しておく必要がある(未定義だとエラー)。- このコールバック関数は、セッションに保存されているユーザー ID からユーザーオブジェクトを再ロードするために呼び出される。この関数が
None
を返すとユーザ ID に該当するユーザが存在しないものと判断される。 - flask-login は
@login_manager.user_loader
のデコレータがついた関数を呼ぶだけなので、情報がどこにあって、どう読み取るかについてはプログラムする必要がある。 - 今回の例では、引数に str 型の ID を受け取り、対応するユーザーオブジェクトを返す。
- このコールバック関数は、セッションに保存されているユーザー 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 に書き込んでおく。
実際のログイン・ログアウトの処理とログインが必要なページの作成は明日の記事にて。