コントローラAuthenticatedSessionController.phpのstore()メソッドの引数には POSTで送られてきたデータがFormRequestを継承したLoginRequestクラスのオブジェクト変数として指定されています。
このstore()うちの処理が/loginの認証処理の基本になっています。
1. LoginRequest
LoginRequestは FormRequestを継承したクラスで、コントローラーに渡す前に$__FORMで送られてきた情報オブジェクト化してバリデーションなど様々な仕事をします。
\app\Http\Request\Auth\LoginRequest.php (全景)
1-1. authorize()とrule()
\app\Http\Request\Auth\LoginRequest.php (1/3)
authorize() がありますが、内容は特に記述されておらず、true が返されています。内容を加えることで認証機能のカスタマイズが可能なようです。
rules() で$requestに適用すべきバリデーションルールが定義され、配列形式で返されます
ここでは「email」「password」に対してバリデーションルールが定義されています。
1-2. authenticate()とattempt()
\app\Http\Request\Auth\LoginRequest.php (2/3)
5ログイン機能の分析①の3-2-④で触れたようにauthenticate()がここに記述されています。
ensureIsNotRateLimited()はPublic Function。後程に定義されていますが、認証回数の制限を行うメソッドです。
次に書かれている Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))
attemptメソッドは、Post されたログイン情報 (Credential) に対して成否を判定するメソッドです。
attemptメソッドの第一引数にはキー/値ペアの配列を指定します。配列にはいったはデータベーステーブルの参照に用いられます。
ここでは具体的には$requestから送られてきたemail をusersテーブルと照合してデータが合致すれば次にpasswordを照合します。
上例のemail、passwordの条件で認証が成功すればユーザーの認証セッションを開始します。
この一連の照合作業はApp\Models\Userモデルを使用して行われ、その際にはEloquentユーザープロバイダが指定されています。
attemptメソッドの第二引数にはブール値を指定し、継続ログインの可否を判断します。
bool=trueの場合はLaravelは無期限に、あるいは手作業でログアウトするまで認証された状態を維持します。
ここで参照を行っているDBのテーブルはusersですがマイグレーションで作ったテーブルにはremember_tokenカラムがあり、「継続ログイン(remenber me)」トークンが収納されています。
とにかくこのattempt()で認証が行われ、その結果がtrue/false で返ってきて、trueだとAuthenticatedSessionControllerにもどり、セッションを再生成してdashboadへの遷移を行います。
なお、Auth::attempt()はAuthファサードから引用されていますが、Illuminate\Support\Facades\Authにはattempt()の記述はありません。
ここではファサードとサービスコンテナに仕組みによりはIlluminate\Auth\SessionGuard.php にあるattempt()が引用されています。
※ファサードとサービスコンテナに仕組みについては別途解説します。
Illuminate\Auth.SessionGuard.php attempt()メソッド
ここに規定されているattempt()の第一引数には確かにDB参照用の配列が指定されていますが、変数名が$credentials になっています。
この時点でバリデーションが完了したデータが指定されているようなのですが、ここまで$requestにバリデーション処理がなされた形跡を見つけることができていません。ここは課題として後日調査とします。
attempt()が不調に終わった場合、
RateLimiter::hit($this->throttleKey()); $thisつまり$request のthrottlekeyがひとつ繰り上がるようです。hit()の解説が部分的にしか見つからなかったのですが、hit()に第二引数としてログインの復活時間を設定することができるそうです。
デフォルトは60秒です。
throw ValidationException::withMessages でemailによる認証が失敗した旨メッセージが返されます。
1-3. 認証回数制御
\app\Http\Request\Auth\LoginRequest.php (3/3)
authenticate()の中に記述されていたensureIsNotRateLimited()がここに規定されています。
RateLimiter::tooManyAttempts($this->throttleKey(), 5)は試行回数が5回以内であればreturnでauthenticate()に戻ります。
5回を超えた場合Lockoutクラスに$this つまり$requestをコンストラクトで渡してLockoutイベントを発火させます。。
$seconds = RateLimiter::availableIn($this->throttleKey()); でキーの試行回数がなくなると、availableInメソッドは試行回数が増えるまでの残り秒数を返します。
throw ValidationException::withMessages() で認証回数が所定値を超えたこと、次回認証可能になるまでの時間等がメッセージとして返されます。
Str::lowerは指定文字列を小文字に変換します。ここでは$this すなわち$requestのemailとipを小文字に変換、さらにStr::transliterateで文字コードセットに音訳してreturnします。
認証が成功した場合は、セッション固定攻撃を防ぐためにユーザーのセッションを再生成します。
2. Laravl認証の構成(ガードとプロバイダ)
Laravelの認証ではGuardという単語が頻繁に出てきます。
Guardは外部からのアクセスの認証に関する機能です。
GuardによってLaravelのログイン機構。つまりアプリケーションにアクセスしてくるユーザをどのような方法で識別するのかということを定義します。
そのための一連のクラスを役割ごとに分類したものが下図になります。
その方法は大きくprovider (プロバイダ:認証方法) と driver (ガードドライバ:認証状態の管理方法) に分類されています。
・ガードドライバ (driver)
ログインの認証状態をどうやって管理するか。
多くの場合はセッション認証 (session) がデフォルトでセットされている。
セッションを利用できないAPI認証の場合は、トークン認証 (token) をセットすることも可能
Auth::extend() で独自のものを追加可能 (AuthServiceProviderに記述)。
ソース上では driver としか表現されていないが、プロバイダの driver と紛らわしいので、
ガードドライバと呼ぶ
app\config\auth.php 'Authentication Guards'
'driver' => 'session'と定義されている
・プロバイダ (provider)
認証の方法
誰 (モデル) をどんなロジック (プロバイダドライバ) で、ログインさせるか
config/auth.php に定義されており、追加・変更が可能
app\config\auth.php 'Authentication Guards'
'provider' => 'users'と定義されている
・モデル (model)
誰をログインさせるか。
多くの場合は、ログインユーザを管理しているテーブルに対応するモデルクラス
このモデルクラスは、 Authenticatable インターフェイスを実装している必要がある
・プロバイダドライバ (driver)
ログイン認証の中核となるロジック。
パスワードをどのように検証し、どのような条件ならログインを許可するか、等を管理
実体は UserProvider クラスと Hasher クラスの組み合わせ
Auth::provider() で独自のものを追加可能 (AuthServiceProviderに記述)
ソース上では driver としか表現されていないが、ガードの driver と紛らわしいので、
プロバイダドライバと呼ぶ
・UserPrvider クラス
Laravel の認証機能における最重要クラス
ログイン画面から Post されたログイン情報 (Credential) に対して成否を判定する
その結果取得されたユーザ情報に対して、ログインをさせてよいかどうかを判定する 等々
あくまでクラスなので自由に作ることができるが、
Illuminate\Contracts\Auth\Authenticatable を implements する必要あり
app\config\auth.php 'User Providers'
'driver' => 'eloquent'、'model' => env('AUTH_MODEL', App\Models\User::class) と定義されている
・Hasher クラス
ユーザ登録時やパスワード変更時に、パスワードをハッシュする
ログイン試行時にパスワードがハッシュ値と一致するかどうかを判定したりするクラス
こちらも自由に作ることができるが、Illuminate\Contracts\Hashing\Hasher を
implements する必要あり
・ゲート (Gate)
いわゆる「権限」
laravel では「認可 (Gate)」 と呼ばれる
ログイン中のユーザが特定の操作を実行できるかどうかを判断する
Gate::define() で定義する (AuthServiceProviderに記述)
2-1. ガード
ガードドライバは、リクエストごとにユーザーを認証する方法を定義しています。
主にLaravelでは、セッションストレージとクッキーを使用してリクエストを管理しています。
リクエストの状態を維持するものが「セッション」ガードです。
2-2. プロバイダ
プロバイダは、永続ストレージ(ここではMySQL)からユーザーを取得する方法を定義します。
LaravelはEloquentとデータベースクエリビルダを使用してユーザーを取得するためのサポートを用意しています。
ただし、アプリケーションの必要性に応じて、追加のプロバイダを自由に定義することができます。
本稿は以上です。Laravelの認証関連のプロセスは正直難しいです。Guardの概念やサービスコンテナの仕組みなど、Breezeの仕組みをオリジナルアプリに展開するためにオブジェクトやメソッドの中身を知りたいと思うのですがリリースノートを含めて完璧な情報がないように思います。
フレームワークによる開発はこういったストレスろ合理的なコーディングというジレンマを克服しながら行う必要がある、ということですね。
次回はガード認証の両輪のひとつ、セッションについて学習してまとめてみたいと思います。
Comments