【Python+Flask】簡易的な認証機能の実装(flask-login)

「【環境構築】【テンプレート】DockerでWebアプリケーション開発環境を構築してみた!!(Nginx+Python+Flask+uWSGI+MySQL)」で記載した内容に加えて、簡易的な認証機能を実装したので紹介します(更新箇所のみを以下、記載します。)。

完成イメージ

「http://127.0.0.1:8000/」へアクセスして表示を確認すると以下、ログイン画面になります。

ログイン画面

ユーザー登録画面

パスワード再設定画面

ログイン後の表示画面

更新について

以下、「★更新」「★新規追加」のファイルを更新しました。

test
├── app
│   ├── Dockerfile
│   └── src
│       ├── app.py ★更新
│       ├── config.py
│       ├── requirements.txt ★更新
│       ├── run.py ★更新
│       ├── templates
│       │   └── list.html
│       │   └── login.html  ★新規追加
│       │   └── resetting_password_cmp.html  ★新規追加
│       │   └── resetting_password.html  ★新規追加
│       │   └── signup.html  ★新規追加
│       ├── users.py ★更新
│       └── uwsgi.ini
├── db
│   ├── init
│   │   └── createdatabase.sql ★更新
│   └── variables.env
├── docker-compose.yml
└── web
    ├── default.conf
    └── nginx.conf

更新ソースコード

app.py

import os
from flask_login import LoginManager
import config
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# Create Flask Application
application = Flask(__name__)

# Set Config Class
application.config.from_object('config.Config')
application.config['SECRET_KEY'] = os.urandom(24)

# Set DB
db = SQLAlchemy(application)

login_manager = LoginManager()
login_manager.init_app(application)

run.py

from app import application, login_manager
from users import Users
from flask import render_template, request, redirect, make_response, Response, session
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required
from werkzeug.security import generate_password_hash, check_password_hash
from app import db
from datetime import timedelta

@login_manager.user_loader
def load_user(user_id):
    return Users.query.get(int(user_id))

'''''''''''''''''
Top画面
'''''''''''''''''
@application.route('/')
def top():
    return redirect('/login')
'''''''''''''''''
ログイン後画面
'''''''''''''''''
@application.route('/after')
@login_required # ログインしているユーザのみアクセス許可
def index():
    users = Users.query.order_by(Users.id).all()
    return render_template('list.html', users=users)

'''''''''''''''''
signup
'''''''''''''''''
@application.route('/signup', methods=['GET', 'POST'])
def signup():
    if request.method == "POST":
        username = request.form.get('username')
        password = request.form.get('password')
        # Userのインスタンスを作成
        user = Users(username=username, password=generate_password_hash(password, method='sha256'), email=username)
        db.session.add(user)
        db.session.commit()
        return redirect('login')
    else:
        return render_template('signup.html')
'''''''''''''''''
login
'''''''''''''''''
@application.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == "POST":
        username = request.form.get('username')
        password = request.form.get('password')
        # Userテーブルからusernameに一致するユーザを取得
        user = Users.query.filter_by(username=username).first()
        if check_password_hash(user.password, password):
            login_user(user)
            return redirect('after')
        else:
            # 入力したユーザー名のパスワードが間違っている場合
            # return "<p>パスワードが間違っています。</p>"
            return Response(status=404, response="ページが見つかりません。")
        
    else:
        return render_template('login.html')
'''''''''''''''''
logout
'''''''''''''''''
@application.route('/logout')
@login_required
def logout():
    logout_user()
    response = make_response(redirect('/'))
    return response

# 追加機能(1) ---------------- start
'''''''''''''''''
パスワード再設定
'''''''''''''''''
# パスワード再設定
# @application.route('/resetting_password')
# def resetting_pass():
#     return render_template('resetting_password.html')
@application.route('/resetting_password', methods=['GET', 'POST'])
def resetting_pass():
    if request.method == "POST":
        email = request.form.get('email')
        # emailに再設定用URLを記載したメールを送信する.
        return render_template('resetting_password_cmp.html')   
    else:
        return render_template('resetting_password.html')
'''''''''''''''''
リクエストの前処理
'''''''''''''''''
@application.before_request
def before_request():
    # リクエストのたびにセッションの寿命を更新する
    session.permanent = True
    application.permanent_session_lifetime = timedelta(minutes=15)
    session.modified = True
# 追加機能(1) ---------------- end

if __name__ == '__main__':
    application.run(host='0.0.0.0', port='8000',debug=True)

users.py

from app import db
from flask_login import UserMixin

class Users(UserMixin, db.Model):
    '''
    Users Table Model
    '''
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(255), nullable=False, unique=True)
    password = db.Column(db.String(255))
    email = db.Column(db.String(255))

    def __init__(self,username,password,email):
        self.username = username
        self.password = password
        self.email = email + '@' + email + '.com'

requirements.txt

Flask
uwsgi
flask-sqlalchemy
PyMySQL
flask-login

createdatabase.sql

USE app;

CREATE TABLE users(
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(255),
    password VARCHAR(255),
    email VARCHAR(255)
);

INSERT INTO users(username,password,email) VALUES('sample','sample','sample@sample.com');
INSERT INTO users(username,password,email) VALUES('test','test','test@test.com');
INSERT INTO users(username,password,email) VALUES('app','app','app@app.com');

GRANT ALL ON app.* TO test;

login.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sample login</title>
</head>
<body>
    <h1>ログイン</h1>
    <form method="POST">
        <label for="">ユーザ名</label>
        <input type="text" name="username">
        <br>
        <label for="">パスワード</label>
        <input type="password" name="password">
        <br>
        <input type="submit" value="ログイン">
    </form>
    <a href="/resetting_password" role="button">パスワードを忘れた方はこちら</a>
    <br>
    <a href="/signup" role="button">ユーザー登録はこちら</a>
</body>
</html>

signup.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sample signup</title>
</head>
<body>
    <h1>ユーザ登録</h1>
    <form method="POST">
        <label for="">ユーザ名</label>
        <input type="text" name="username">
        <br>
        <label for="">パスワード</label>
        <input type="password" name="password">
        <br>
        <input type="submit" value="新規登録">
    </form>
</body>
</html>

resetting_password.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sample login</title>
</head>
<body>
    <h1>パスワードを再設定する</h1>
    <p>登録したメールアドレスを入力してください</p>
    <form method="POST">
        <label for="">メールアドレス</label>
        <input type="text" name="email">
        <br>
        <input type="submit" value="続ける">
    </form>
    <a href="/" role="button">トップページに戻る</a>
</body>
</html>

resetting_password_cmp.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sample login</title>
</head>
<body>
    <h1>メールを確認する</h1>
    <p>パスワード再設定用メールを送信しました。24時間以内にメールに記載されたリンクをクリックして手続きを行ってください。</p>
    <a href="/" role="button">トップページに戻る</a>
</body>
</html>

補足事項

  • ユーザー登録では、同じユーザー名の登録はできます。あくまで、簡易的な認証機能の実装になるため、各自、仕様を追加して実装ください。
  • ログイン失敗時は、簡易的なページを表示しているため、各自カスタマイズしてください。
  • 各画面の入力チェック(バリデーション等)の処理は実装していないため、各自実装ください。
タイトルとURLをコピーしました