6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Flask】ログイン機能を追加

Posted at

設定方法

LoginManager の設定

LoginManagerは、ユーザーのログイン状態を管理してくれる

from flask_login import LoginManager

#インスタンス化
login_manager = LoginManager()
#アプリをログイン機能を紐付ける
login_manager.init_app(app)
#未ログインユーザーを転送する(ここでは'login'ビュー関数を指定)
login_manager.login_view = 'login'

#現在のログインユーザーの情報を保持し、必要なときに参照できるようになる。
@login_manager.user_loader
def load_user(user_id):
    return User.query.get(user_id)

モデルの設定

UserMixinでログインに必要な機能を継承する。
werkzeugでパスワードハッシュ化とチェック機能を追加。

#UserMixin、ログイン・ログアウトで必要なライブラリをインポート
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
#パスワードをハッシュ化するライブラリをインポート
from werkzeug.security import check_password_hash, generate_password_hash
#UserMixinをを継承
class User(db.Model, UserMixin):
  __tablename__ = 'user'
  id = db.Column(db.Integer, primary_key = True)
  email = db.Column(db.String(64), unique=True, index=True)
  username = db.Column(db.String(64), unique=True, index=True)
  password_hash = db.Column(db.String(128))

  #パスワードチェックする関数を追記
   def check_password(self, password):
    return check_password_hash(self.password_hash, password)

ログイン用フォーム

class LoginForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(), Email(message='正しいメールアドレスを入力してください')])
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField('ログイン')

ビュー関数

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        #フォーム入力したアドレスがDB内にあるか検索
        user = User.query.filter_by(email=form.email.data).first()
        if user is not None:
            #check_passwordはUserモデル内の関数
            if user.check_password(form.password.data):
                #ログイン処理。ログイン状態として扱われる。
                login_user(user)
                next = request.args.get('next')
                if next == None or not next[0] == '/':
                    next = url_for('user_maintenance')
                return redirect(next)
                
            else:
                flash('パスワードが一致しません')
        else:
            flash('入力されたユーザーは存在しません')

    return render_template('login.html', form=form)

未ログイン状態でログインが必要なページを訪れた場合は、クエリー文字列のあとログイン後に遷移するページとしてそのページのURLが表示される。
例) "/register"がログイン必須のページだった場合、以下のURLが表示される。
/login?next=%2Fregister
 "%2F"は、"/"(スラッシュ)の意味

この場合、ログイン成功したら、"/register"に遷移する。

そのため、クエリー文字列に"next"がない、もしくはnextの1文字目が"/"ではない場合は、
ユーザー一覧画面へ遷移する。
"next"がない = 「通常どおり、ログイン状態でアクセスしようとした」 ので、ログイン後に本来リダイレクトすべきページに遷移する。

"next"がある = 「未ログインでログイン必須のページにアクセスしようとした」ので、そのユーザーがアクセスしようとしたページにリダイレクトさせる。

上記の条件分岐は以下のようなコードとなる。

next = request.args.get('next')
if next == None or not next[0] == '/':
    next = url_for('user_maintenance')
return redirect(next)

ログインページのテンプレート

<h2>ログインページ</h2>
<div>
  <a href="{{ url_for('register' )}}">
      新規登録へ
  </a>
</div>

<form method="POST">
    <!-- CSRF対策(トークンが生成される) -->
  {{ form.hidden_tag() }}
  <div>
    {{ form.email.label }} {{ form.email() }}
    {% for error in form.email.errors %}
    <span style="color: red;">{{ error }}</span>
    {% endfor %}
  </div>
  <div>
    {{ form.password.label }} {{ form.password() }}
    {% for error in form.password.errors %}
    <span style="color: red;">{{ error }}</span>
    {% endfor %}
  </div>
    {{ form.submit() }}
</form>

ログインユーザーを表示

is_authenticated はログインしているかどうかを判断する関数。
モデルにUserMixinを継承することで使えるようになる。

  {% if current_user.is_authenticated %}

    <a class="nav-link" href="{{ url_for('logout') }}">ログアウト</a>

    {{ current_user.username }}

  {% endif %}

新規ユーザー登録時に、パスワードのハッシュ化

Python Property・・・クラスのインスタンスに保持するデータで、値の参照や変更方法を制限することが可能

  #値の参照
  @property
  def password(self):
    raise AttributeError('password is not a readable attribute')
  #値の設定
  @password.setter
  def password(self, password):
      self.password_hash = generate_password_hash(password)

モデルクラスの初期設定の関数も修正。
"password_hash"から"hash"を取り除き、passwordはハッシュ化されていない状態で渡されることを明示する。
setterでパスワードを受け取ってハッシュ化して、その値を"password_hash"に入れられデータベースに登録されるという流れ。

  def __init__(self, email, username, password):
    self.email = email
    self.username = username
    self.password = password

新規登録のビュー関数"register"も修正。
モデルクラスをインスタンス化する際の引数"password_hash"から"hash"を取り除く。

@app.route("/register", methods=["GET", "POST"])
def register():
  form = RegistrationForm()
  
  if form.validate_on_submit():
    user = User(email=form.email.data, username=form.username.data, password=form.password.data)
    db.session.add(user)
    db.session.commit()

ログアウト機能

@app.route('/logout')
def logout():
    #現在のユーザーをログアウト状態にする
    logout_user()
    return redirect(url_for('login'))

ログインユーザーのみアクセス可能にする

login_required をインポート

from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required

@login_required を付けることで、未ログインユーザーはログインページに転送されるようになる。

@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('login'))

以下のコードでリダイレクトするビュー関数と、エラーメッセージを定義する

#ビュー関数"login"にリダイレクトするよう定義
login_manager.login_view = 'login'

# 未ログインユーザーにメッセージ表示
def localize_callback(*args, **kwarg):
    return 'このページにアクセスするには、ログインが必要です'
login_manager.localize_callback = localize_callback
6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?