Authentication with Flask

salt and hash

Authentication with Flask で検索し、ブログや動画を参考にしながら、前回作ったFlask Blog Appに登録・ログイン・ログアウト機能を実装していきます。

user_id = 1 のユーザーをadminとし、投稿・編集・削除・コメント権限を持たせ、それ以外のユーザーにはコメント権限のみを付与する予定です。(コメント機能は、今回実装していません)

大まかな流れ

以下、Digital Ocean Tutorialをまとめてみました。

  1. Flask認証パッケージとSQLAlchemy(ユーザーデータベース)を使う
  2. メインアプリ(認証まわりの動き)をざっくり書いて、データベースと紐づける
  3. 登録・ログイン・ログアウトの Routes を設定して各ページに飛ばす
  4. それぞれのテンプレート(HTML)を作成する
  5. データベースに保存するモデルを作成する
  6. 登録画面・認証機能・データベースを紐づける
  7. Flask-Loginを機能させるため、UserMixinを追加する
  8. user loaderに「データベースからユーザー探してきて」と指定する

参考サイト:How To Add Authentication to Your App with Flask-Login

参考サイト:Flask-Login

参考動画:Python Flask Tutorial 6 – Add Authentication to Web App with Flask-Login

参考動画:Authentication and Authorization With Flask-Login


パスワードのハッシュ化とソルト

ハッシュ化: 入力した値を「分かりづらい文字列に変換する関数」にかけること。「わかりづらい文字列」とは言え、入力値が同じだと、同じ文字列が返ってくる。

ソルト: 元のパスワード前後に文字列を加えることで、ハッシュ化したパスワードを更に強靭にすること。

ソルト + ハッシュ化 = 同じパスワードを持つユーザーがいても、ソルトのお陰で、ユーザーごとに違った値(パスワード)を保管する形になる

ソルト・ラウンド

ラウンド1 (パスワード + ソルト + ハッシュ関数)=「わかりづらい文字列A」

ラウンド2 (「分かりづらい文字列A」+ ソルト + ハッシュ関数)=「分かりづらい文字列B」

ラウンド3  (「分かりづらい文字列B」+ ソルト + ハッシュ関数)=「分かりづらい文字列C」

上記のラウンドを、繰り返せば繰り返すほど、「分かりづらい文字列」が、「分かるまで数年かかるレベル」になっていく。

パスワードのハッシュ化とソルトは、以下の様に指定する

from werkzeug.security import generate_password_hash
      
good_password = generate_password_hash(
    form.password.data,   # 登録フォームのパスワードデータを取ってくる
    method='pbkdf2:sha256',   # ハッシュ関数
    salt_length=10 
    )

GeneratePasswordHash メソッドではパスワードとする文字列と、ソルトとする文字列を渡したらそれらと繋げ、その値について SHA256 でハッシュを計算します。 SHA256 は任意の長さの入力に対して、256 ビット固定長のハッシュを返します。 ・・・<中略>・・・ PBKDF2 というのは Password-Based Key Drivation Function 2 (パスワードベース鍵導出関数 2) の略で、 RSA 研究所の公開鍵暗号化標準仕様 (特に PKCS#5 パスワードに基づく暗号化の標準) の一部で、RFC 2898 として提案されている方法です。この方法ではストレッチングのための、ハッシュの繰り返し計算回数を指定できます。

C# による SHA256 と PBKDF2 によるソルト付きパスワードのハッシュ計算方法

データベースを開いてみると、単純なパスワードが「分かりづらい文字列」に変換されて保存されている!!

参考サイト:ソルト【パスワード】(英:salt)とは

参考動画:Hashing Passwords With Werkzeug – Flask Fridays #13

公式ドキュメント:werkzeug

ブログアプリにユーザー追加

では早速、大まかな流れに沿って、ユーザーが、登録・ログイン・ログアウトできるようにしてみます。

パスワードもソルト+ハッシュ化して保存してみます。

流れ1: Flask認証パッケージとSQLAlchemy(ユーザーデータベース)を使う

pip3 install flask flask-sqlalchemy flask-login

User と Post の relationship (関係)については、こちらの動画がおススメです。

#省略
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import relationship

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

class User(UserMixin, db.Model):  # UserMixinでログインしているユーザーの情報を保持する
    __tablename__ = "users"
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(100), unique=True)
    password = db.Column(db.String(100))
    name = db.Column(db.String(100))
    posts = relationship("Post", back_populates="author")  # Postと関連づけ、authorとuserを紐づけている


class Post(db.Model):
    __tablename__ = "blog_posts"
    id = db.Column(db.Integer, primary_key=True)
    author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
    author = relationship("User", back_populates="posts")  # Userと関連付け、authorとpostを紐づけている
    title = db.Column(db.String(250), unique=True, nullable=False)
    subtitle = db.Column(db.String(250), nullable=False)
    date = db.Column(db.String(250), nullable=False)
    body = db.Column(db.Text, nullable=False)
    img_url = db.Column(db.String(250), nullable=False)

# 省略

新しいユーザーが登録できる様、「forms.py」に登録フォーム(RegisterForm)、「templates」フォルダに register.html を作成する。

※ フォームは、前回も使用した「Flask-WTF」を使用します:

 pip3 install Flask-WTF

Flask-WTFをインストールしたら、forms.pyでインポートします。

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField
from wtforms.validators import DataRequired, URL

class RegisterForm(FlaskForm):
    email = StringField("Email", validators=[DataRequired()])
    password = PasswordField("Password", validators=[DataRequired()])
    name = StringField("Name", validators=[DataRequired()])
    submit = SubmitField("Sign Up")
流れ2・3・4:
  • メインアプリ(認証まわりの動き)をざっくり書いて、データベースと紐づける
  • 登録・ログイン・ログアウトの Routes を設定して各ページに飛ばす
  • それぞれのテンプレート(HTML)を作成する
  • 登録・ログインについて、上記流れ2~4を記述していく。以下①②③を繰り返す。ログアウトはフォーム不要。

    <①フォーム ②@app.route(‘登録とか’)と動きの設定 ③ HTMLでフォーム表示>

    ※ ここで、パスワード「ソルト+ハッシュ化」 も設定
    @app.route('/register', methods=["GET", "POST"])
    def register():
        form = RegisterForm()
        if form.validate_on_submit():
    
            good_password = generate_password_hash(    # ここでパスワードをgood_password名づけてハッシュ化
                form.password.data,
                method='pbkdf2:sha256',
                salt_length=8
            )
            new_user = User(
                email=form.email.data,
                name=form.name.data,
                password=good_password,   # パスワードを「good_password」で保存
            )
            db.session.add(new_user)
            db.session.commit()
            login_user(new_user)
            return redirect(url_for("get_all_posts"))
    
        return render_template("register.html", form=form, current_user=current_user)

    register.html で、wtf を表示させる。

    {% extends 'bootstrap/base.html' %}
    {% import "bootstrap/wtf.html" as wtf %}
    
    省略
    
     <div class="container">
        <div class="row">
          <div class="col-lg-8 col-md-10 mx-auto">
           
           {{ wtf.quick_form(form, novalidate=True, button_map={"submit": "primary"}) }}
    
          </div>
        </div>
      </div>
    
    省略

    登録したユーザーがログイン出来るようにするため、①「ログインフォーム」②「@app.route(‘/login’, methods=[“GET”, “POST”])」③「login.html」を記述する

    class LoginForm(FlaskForm):
        email = StringField("Email", validators=[DataRequired()])
        password = PasswordField("Password", validators=[DataRequired()])
        submit = SubmitField("Login")

    以下、登録されたユーザーをデータベースから見つけてログインさせる。

    この時、登録時に「ソルト+ハッシュ化」したパスワードをチェックする:「check_password_hash()」

    from flask_login import login_user, logout_user
    
    login_manager = LoginManager() # flask-loginの初期化
    login_manager.init_app(app) # flask-loginの初期化
    
    @login_manager.user_loader  # ユーザーを user_id でロードする
    def load_user(user_id):
        return User.query.get(int(user_id))
      
    @app.route('/login', methods=["GET", "POST"])
    def login():
        form = LoginForm()
        if form.validate_on_submit():
            email = form.email.data
            password = form.password.data
    
            user = User.query.filter_by(email=email).first()
            
            if user and check_password_hash(user.password, password):  # ここでハッシュ化されたPWをチェック
                login_user(user)
                return redirect(url_for('get_all_posts'))
              
        return render_template("login.html", form=form)

    login.htmlで、wtf を表示させる(register.htmlと同じ作業)

    {% extends 'bootstrap/base.html' %}
    {% import "bootstrap/wtf.html" as wtf %}
    
    # 省略
           <div>
            {{ wtf.quick_form(form, novalidate=True, button_map={"submit": "primary"}) }}
          </div>
    
     # 省略

    最後にログアウト。

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

    今回のまとめ

    上記以外にも、flask-loginでは、remember me (cookie使用)、current_user、login_requiredなど、便利な機能が簡単に使えるようです。また、エラーメッセージなどは、flash で簡単に表示させれるので、これも便利!!

    おすすめ記事

    2件のコメント

    コメントを残す

    メールアドレスが公開されることはありません。 * が付いている欄は必須項目です