エンジニアライフスタイルブログを運営しているミウラ(@miumiu06171)です。
普段はフリーランスでシステムエンジニアをしております。
今回は、こちらの記事に続いてPythonのFlaskで作成したブログアプリケーションに対してサインアップ・ログイン・ログアウト機能を追加する方法を紹介していきます。
FlaskでWebアプリを作成するためにVisual Studio Code (VS Code) を使用しているため、同様に確認したい方は、こちらの記事でVS CodeでPythonの開発環境を構築してみてください。
サインアップ機能とは(signup)
サインアップ(sign up)とは、サービスに登録することを言います。
わかりやすい具体例をあげると、Amazon、楽天市場などのWebサービスを利用する上で、最初にユーザー登録を行いますが、まさにこれがサインアップ機能です。
ログイン機能とは(login)
ログイン機能とは、あらかじめ登録しておいたIDやパスワードを使って本人確認を行う機能です。
一般的なWebサービスでは、前述したサインアップを一度行った後、サービスを利用するときにIDやパスワードを使ってログインを行ってサービスを利用できる状態にすることが基本な流れとなっています。
そのため、Webアプリケーションを作成する上でサインアップ機能とログイン機能を実装する方法は必ず知ってく必要があります。
今回は、別記事で作成したブログアプリケーションに対して、サインアップ機能、ログイン機能、ログアウト機能の3つを実装する方法について解説していきます。
PythonのFlaskでサインアップ・ログイン・ログアウト機能付きWebアプリケーション作成
事前準備
ログイン機能、サインアップ機能を実装するための事前準備から見ていきましょう。
flask-loginインストール
VS Codeのコンソールを開き、以下のPythonのpipコマンドでFlaskの拡張機能である「flask-login」をインストールします。
1 |
pip install flask-login |
flask-loginの詳細な仕様については、こちらを参照してください。
flask-loginのLoginManagerとflaskアプリの紐付け
ユーザー管理DBのテーブル定義ができたら、次はflask-loginのLoginManagerとFlaskアプリを紐付けていきます。
1 2 3 4 5 6 7 8 9 |
from flask_login import LoginManager app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db' db = SQLAlchemy(app) login_manager = LoginManager() login_manager.init_app(app) |
1行目と8行目では、flask_loginパッケージの中からLoginManagerをインポートし、インスタンス化しています。
9行目では、LoginManagerのインスタンスのinit_appメソッドを使って、Flaskアプリと紐付けを行っています。
これで拡張機能であるflask-loginとFlaskアプリを紐付けられたことになります。
flaskアプリの’SECRET_KEY’コンフィグレーションを定義
現在ユーザーがログイン中なのか、ログアウト状態なのかなどのセッション情報を管理するため、Flaskアプリのコンフィグレーションに秘密鍵(SECRET_KEY)を定義する必要あります。
1 2 3 4 5 6 7 8 9 10 11 |
import os from flask_login import LoginManager app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db' app.config['SECRET_KEY'] = os.urandom(24) db = SQLAlchemy(app) login_manager = LoginManager() login_manager.init_app(app) |
秘密鍵は、他人が容易に推測できるものであってはなりません。
そのため、上記コードのようにosモジュールをインポートし、osモジュールのurandomメソッドを使用してランダムな値を秘密鍵に指定しています。
ユーザー管理用データベースのテーブル定義
前回作成したブログアプリケーションにサインアップ機能、ログイン機能を実装するためにユーザー管理が必要になります。
そのため、ユーザー管理用データベースのテーブルを準備していきます。
復習になりますが、PythonでDBのテーブルを定義するには、sqlalchemyを使ったクラス定義で出来ましたね。
今回は、Userクラスを作成し、ユーザー名とパスワードを以下のように定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import os from flask_login import UserMixin, LoginManager app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db' app.config['SECRET_KEY'] = os.urandom(24) db = SQLAlchemy(app) login_manager = LoginManager() login_manager.init_app(app) class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(50), nullable=False, unique=True) password = db.Column(db.String(25)) |
2行目では、flask-loginからUserMixinというクラスをインポートしています。
14~17行目では、DBのテーブルに対応するUserクラスを作成しています。
このUserクラスは、flask-loginのUserMixinクラスを継承させることで、flask-loginに含まれるユーザー管理に必要な機能を利用できるようにしています。
15行目ではユーザーを識別するためのプライマリーキーを定義し、16~17行目ではユーザー名とパスワードのカラムをそれぞれ定義しています。
これでUserクラスの定義は完了です。
ユーザー管理用データベースのテーブル作成
ユーザー管理用データベースのテーブル定義が出来たところで、実際にテーブルを実体化してみましょう。
テーブルを作成する手順は、VSCodeのコンソール上で以下のコマンドを実行します。
1 2 3 4 5 6 7 8 |
(venv) PS C:\Users\Gemini\Desktop\virtual\Flask> python Python 3.9.7 (tags/v3.9.7:1016ef3, Aug 30 2021, 20:19:38) [MSC v.1929 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> from app import db C:\Users\Gemini\Desktop\virtual\Flask\venv\lib\site-packages\flask_sqlalchemy\__init__.py: SADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by defn th e future. Set it to True or False to suppress this warning. warnings.warn(FSADeprecationWarning( >>> db.create_all() >>> exit() |
まずは、「python」または「python3」を実行し、Pythonの対話モードに遷移させます。
app.pyのdb変数を読み込んだ後、dbのcreate_allメソッドでデータベースを作成します。
データベースの作成が完了したら、exit()コマンドでPythonの対話モードを終了します。
Flaskでサインアップ機能実装(signup)
ここでは、サインアップ機能の実装方法を紹介していきます。
signup.htmlの作成
まずは、サインアップのUIを実装するためにsignup.htmlを作成します。
signup.htmlは、前回作成したcreate.htmlをベースとし、以下のとおり作成します。
1 2 3 4 5 6 7 8 9 10 11 |
{% extends 'base.html' %} {% block content %} <h1>ユーザ登録</h1> <form method="POST"> <label for="">ユーザ名</label> <input type="text" name="username"> <label for="">パスワード</label> <input type="password" name="password"> <input type="submit" value="新規登録"> </form> {% endblock %} |
3行目では、h1タグのタイトルを「ユーザー登録」に変更しています。
5,7行目では、ラベル名を「ユーザー名」「パスワード」にそれぞれ変更しています。
6,8行目では、inputタグのname属性を「username」「password」に変更しています。
app.pyにサインアップ機能作成
サインアップのUIを実装できたら、app.pyにサインアップ機能の内部処理を実装していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from flask import Flask from flask import render_template, request, redirect from flask_login import UserMixin, LoginManager from werkzeug.security import generate_password_hash @app.route('/signup', methods=['GET', 'POST']) def signup(): if request.method == "POST": username = request.form.get('username') password = request.form.get('password') # Userのインスタンスを作成 user = User(username=username, password=generate_password_hash(password, method='sha256')) db.session.add(user) db.session.commit() return redirect('/login') else: return render_template('signup.html') |
7行目で「/signup」というルーティングを定義し、GET/POSTメソッドを許可しています。
17~18行目でGETメソッド時の処理を記載しており、signup.htmlを呼び出してサインアップのホーム画面を表示しています。
9~16行目でPOSTメソッド時の処理を記載しております。
10~11行目では、signup.htmlのフォームに入力されたユーザー名、パスワードを取得しています。
13行目で先程取得したユーザー名、パスワードを使ってUserクラスの新しいインスタンスを生成しています。
ここで注目したい点は、パスワードのハッシュ化です。
ユーザー管理においてDBにパスワードを保存する際、プレーンテキストをそのまま保存するのではなく、セキュリティ向上のためにパスワードをハッシュ化して保存することが通例です。
パスワードのハッシュ化を行うため、4行目でwerkzeugパッケージのgenerate_password_hashメソッドをインポートし、generate_password_hashを使ってsha256という暗号化アルゴリズムのハッシュ値をパスワードに設定しています。
14~15行目では、Userクラスの新しいインスタンスをデータベースに書き込んでいます。
16行目では、サインアップ処理が完了した後にログインのホーム画面にリダイレクトしています。
ログインのホーム画面については、後述します。
Flaskでログイン機能実装(login)
ここでは、ログイン機能の実装方法を紹介していきます。
login.htmlの作成
まずは、ログインのUIを実装するためにlogin.htmlを作成します。
login.htmlは、前回作成したsignup.htmlをベースとし、以下のとおり作成します。
1 2 3 4 5 6 7 8 9 10 11 |
{% extends 'base.html' %} {% block content %} <h1>ログイン画面</h1> <form method="POST"> <label for="">ユーザ名</label> <input type="text" name="username"> <label for="">パスワード</label> <input type="password" name="password"> <input type="submit" value="ログイン"> </form> {% endblock %} |
3行目では、h1タグのタイトルを「ログイン画面」に変更しています。
9行目では、ボタンの文字を「ログイン」に変更しています。
app.pyにログイン機能作成
ログインのUIを実装できたら、app.pyにログイン機能の内部処理を実装していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from flask import Flask from flask import render_template, request, redirect from flask_login import UserMixin, LoginManager, login_user from werkzeug.security import generate_password_hash, check_password_hash @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == "POST": username = request.form.get('username') password = request.form.get('password') # Userテーブルからusernameに一致するユーザを取得 user = User.query.filter_by(username=username).first() if check_password_hash(user.password, password): login_user(user) return redirect('/') else: return render_template('login.html') |
7行目で「/login」というルーティングを定義し、GET/POSTメソッドを許可しています。
17~18行目でGETメソッド時の処理を記載しており、login.htmlを呼び出してログインのホーム画面を表示しています。
9~16行目でPOSTメソッド時の処理を記載しております。
10~11行目では、login.htmlのフォームに入力されたユーザー名、パスワードを取得しています。
13行目では、フォームから取得したユーザー名、パスワードと一致するユーザーを取得しています。
14行目では、フォームから取得したユーザー名、パスワード(プレーンテキスト)を使って、データベースのユーザー名、パスワード(プレーンテキストをsha256でハッシュ化した値)と一致するユーザーが存在するかチェックしています。
ここで注目したい点は、データベースのパスワードがプレーンテキストではなく、sha256でハッシュ化された値であることです。
フォームから取得したパスワード(プレーンテキスト)と、データベースに保存したパスワード(プレーンテキストをsha256でハッシュ化した値)が一致することを確認するため、werkzeugパッケージのcheck_password_hashメソッドを使用しています。
15行目では、フォームの情報とデータベースの情報が一致したら、flask_loginパッケージのlogin_userメソッドでログインを行います。
16行目では、ログイン処理が成功した後にブログアプリケーションのホーム画面にリダイレクトしています。
Flaskでログアウト機能実装(logout)
ここでは、ログアウト機能の実装方法を紹介していきます。
index.htmlの編集
ログアウトのUIを実装するためにindex.htmlを編集します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{% extends 'base.html' %} {% block content %} <h1>ブログアプリケーション</h1> <a href="/create" role="button">新規作成画面</a> <a href="/logout" role="button">ログアウト</a> {% for blogarticle in blogarticles %} <article> <h2>{{ blogarticle.title }}</h2> <a href="/update/{{ blogarticle.id }}" role="button">編集</a> <a href="/delete/{{ blogarticle.id }}" role="button">削除</a> <p>作成日時:{{ blogarticle.created_at }}</p> <p>{{ blogarticle.body }}</p> </article> {% endfor %} {% endblock %} |
6行目にログアウトのボタンを追加しています。
app.pyにログアウト機能作成
ログアウトのUIを実装できたら、app.pyにログアウト機能の内部処理を実装していきます。
1 2 3 4 5 6 7 8 9 10 11 |
from flask import Flask from flask import render_template, request, redirect from flask_login import UserMixin, LoginManager, login_user, logout_user, login_required from werkzeug.security import generate_password_hash, check_password_hash @app.route('/logout') @login_required def logout(): logout_user() return redirect('/login') |
7行目で「/logout」というルーティングを定義しています。
3行目では、flask_loginパッケージからlogout_user、login_requiredを追加でインポートしています。
8行目では、先程インポートしたlogin_requiredデコレータを指定することで、ログアウト機能がログイン状態で実行されるように指定しています。
10行目では、logout_userメソッドでログアウト処理を実行しています。
11行目では、ログアウトに成功した後、ログインのホーム画面にリダイレクトしています。
実際のブログWebアプリケーションに近づける
サインアップ機能、ログイン機能、ログアウト機能を実装してきました。
ここでは、さらに実際のブログWebアプリケーションに近づけるための実装を行います。
アクセス制限の追加(@login_required)
実Webアプリケーションに近づけるためにアクセス制限を追加します。
ログアウト機能の実装で行ったようにlogin_requiredデコレータを使って、各機能のアクセス制限の条件を見直します。
以下の処理についてもログインしているユーザのみが実行できる機能であるため、login_requiredデコレータを付加します。
・ブログトップページ(/)
・ブログの作成処理(create)
・ブログの更新処理(update)
・ブログの削除処理(delete)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
@app.route('/', methods=['GET']) @login_required def blog(): if request.method == 'GET': # DBに登録されたデータをすべて取得する blogarticles = BlogArticle.query.all() return render_template('index.html', blogarticles=blogarticles) @app.route('/create', methods=['GET', 'POST']) @login_required def create(): if request.method == "POST": title = request.form.get('title') body = request.form.get('body') # BlogArticleのインスタンスを作成 blogarticle = BlogArticle(title=title, body=body) db.session.add(blogarticle) db.session.commit() return redirect('/') else: return render_template('create.html') @app.route('/update/<int:id>', methods=['GET', 'POST']) @login_required def update(id): # 引数idに一致するデータを取得する blogarticle = BlogArticle.query.get(id) if request.method == "GET": return render_template('update.html', blogarticle=blogarticle) else: # 上でインスタンス化したblogarticleのプロパティを更新する blogarticle.title = request.form.get('title') blogarticle.body = request.form.get('body') # 更新する場合は、add()は不要でcommit()だけでよい db.session.commit() return redirect('/') @app.route('/delete/<int:id>', methods=['GET']) @login_required def delete(id): # 引数idに一致するデータを取得する blogarticle = BlogArticle.query.get(id) db.session.delete(blogarticle) db.session.commit() return redirect('/') |
user_loaderを追加
ブログWebアプリケーション実行中のセッションからユーザー情報を読み取れるようにlogin_managerのuser_loaderが必須です。
1 2 3 4 5 6 7 8 9 10 |
from flask_login import UserMixin, LoginManager, login_user, logout_user, login_required login_manager = LoginManager() login_manager.init_app(app) @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) |
上記を追記することでログインしてアクセスした際の最新のユーザー情報を読み込んでくれます。
PythonのFlaskで作成したサインアップ・ログイン・ログアウト機能の動作確認
サインアップ動作確認
サインアップサイト(http://127.0.0.1:5000/signup)にアクセスしてユーザ作成してみましょう。
ユーザー名に「miura」、パスワードに「<任意の文字列>」を設定し、「新規登録」ボタンを押します。
「新規登録」ボタンによりユーザー作成が完了した後、ログインサイトにリダイレクトされていることがわかります。
ログイン動作確認
ログインサイト(http://127.0.0.1:5000/login)にアクセスし、ログインできるか試してみましょう。
ユーザー名に「miura」、パスワードに「<任意の文字列>」を設定し、「ログイン」ボタンを押します。
無事ログインに成功した後、ブログアプリケーションのホーム画面が表示されていることがわかります。
ログアウト動作確認
ログインサイトからログインし、ブログアプリケーションサイトが表示された状態からログアウト機能を試してみましょう。
「ログアウト」ボタンを押します。
ログアウトボタンを押した後、ログインページにリダイレクトされていることがわかります。
まとめ
いかがでしたでしょうか。
PythonのFlaskで作成したブログアプリケーションに対して、サインアップ機能、ログイン機能、ログアウト機能を追加で実装してきました。
これら3つの機能は、ブログWebアプリケーションに限らず、どのWebアプリケーションでも基本機能として備わっていることが多いため、本記事で実装方法を習得頂ければと思います。
【関連記事】
【Python Flask入門】使い方の基礎からWebアプリケーション作成までの流れ
【Python Flask入門】データベースと連携したWebアプリケーション作成