認証周りが絡む特殊な実装をしようとしたときに、色々と調べてみたのでせっかくだしまとめます☺️
Guard の使い方等は 「認証」ドキュメントをご参考ください。
[Laravel 5.8 認証]
(https://readouble.com/laravel/5.8/ja/authentication.html)
Guardってどうやって使うんだったっけ?(おさらい)
'guards' => [
'guard-name' => [
'driver' => 'session',
'provider' => 'users',
],
],
こんな風に guards
設定しておいて、
Route::get('profile', function() {
})->middleware('auth:guard-name');
ルートで middleware を呼ぶと、指定した Guard で 認証されたユーザーだけにアクセス許可 します🔑
Guard クラスはどうやって呼び出すのじゃ?
'driver' => 'session'
のように、 session / token 等の Guardクラス の driver を指定します。
独自の カスタム Guard やパッケージで追加した Guard を指定することも可能です。
driver って何なのじゃ?
Guard に付けられた名前ぐらいに思っておけば大丈夫そうです。
カスタムGuard の場合は、 Guard インスタンスを返すクロージャでした!
(詳しくは↓ カスタム Guard はどうやって登録されるのじゃ? 参照)
カスタム Guard はどうやって登録されるのじゃ?
カスタム Guard を登録するには、ドライバ名と Guard (を継承・実装した)クラスオブジェクトを返すクロージャを、Auth::extend()
に渡します。
public function boot()
{
$this->registerPolicies();
Auth::extend('jwt', function($app, $name, array $config) {
return new JwtGuard(Auth::createUserProvider($config['provider']));
});
}
ここで呼び出されている Auth::extend()
では、以下のように driver を登録しています。
public function extend($driver, Closure $callback)
{
$this->customCreators[$driver] = $callback;
return $this;
}
認証時等に同クラスの callCustomCreator()
が呼び出され、登録した Guard インスタンス を返します。
protected function callCustomCreator($name, array $config)
{
return $this->customCreators[$config['driver']]($this->app, $name, $config);
}
SessionGuard や TokenGuard はどのように呼ばれるのじゃ?
デフォルトで用意されている session
と guard
用には createSessionDriver()
createTokenDriver()
が AuthManager
内に用意されており、この中で Guard クラスから new していました😃
public function createSessionDriver($name, $config)
{
$provider = $this->createUserProvider($config['provider'] ?? null);
$guard = new SessionGuard($name, $provider, $this->app['session.store']);
// 略
return $guard;
}
Guardクラス は何をしているのじゃ?
アプリケーションから Guard を呼び出すには、基本的に Auth
ファサードを使います。
Auth
ファサードは、 Guardインスタンスに静的なインターフェイス を提供しています。
Auth::login()
は Guardインスタンスの login()
、 Auth::user()
はGuardインスタンスの user()
を呼び出しています。
ただし Auth::login()
等で呼び出すのは auth.php
でdefault に指定したguardです。
それ以外の guard を呼び出すには Auth::guard('api')->login()
のように guard 名を指定する必要があります。
SessionGuard は何をしているのじゃ?
ではここで、使用頻度が高そうな SessionGuard
を深掘りしてみましょう🙌
よく使うメソッドをいくつか選んでみましたが実際にはもっとたくさんありますので興味がある方は SessionGuard を読んでみてください☺️
attempt (ログインを試みる)
public function attempt(array $credentials = [], $remember = FALSE)
{
$this->fireAttemptEvent($credentials, $remember);
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
if ($this->hasValidCredentials($user, $credentials))
{
$this->login($user, $remember);
return TRUE;
}
$this->fireFailedEvent($user, $credentials);
return FALSE;
}
イベント発火🔥等しつつ、 credentials をチェックして( $this->provider->retrieveByCredentials()
)正しければログイン処理( $this->login()
)しているようです。
credentials のチェック・ユーザー取得部分は Provider のお仕事なので後述します✍️
login (ログインする)
⬆️ の attempt()
からも呼ばれるし、 Auth::login
で呼ぶこともありますね。
public function login(AuthenticatableContract $user, $remember = FALSE)
{
$this->updateSession($user->getAuthIdentifier());
if ($remember)
{
$this->ensureRememberTokenIsSet($user);
$this->queueRecallerCookie($user);
}
$this->fireLoginEvent($user, $remember);
$this->setUser($user);
}
- Sessionの更新
- Remember Me 関連のゴニョゴニョ
- イベント発火
- インスタンスにデータの保持
概ねイメージ通りでした。
user (ログイン済ユーザーを取得する)
public function user()
{
if ($this->loggedOut)
{
return;
}
if ( ! is_null($this->user))
{
return $this->user;
}
$id = $this->session->get($this->getName());
if ( ! is_null($id) && $this->user = $this->provider->retrieveById($id))
{
$this->fireAuthenticatedEvent($this->user);
}
if (is_null($this->user) && ! is_null($recaller = $this->recaller()))
{
$this->user = $this->userFromRecaller($recaller);
if ($this->user)
{
$this->updateSession($this->user->getAuthIdentifier());
$this->fireLoginEvent($this->user, TRUE);
}
}
return $this->user;
}
$this->user
が既にあればそのユーザーを、ない場合はセッションからユーザーIDを取り出して Provider に渡し、ユーザーオブジェクトを取得・返却しています。
なおセッションには以下のキーで保持されているようです。
public function getName()
{
return 'login_' . $this->name . '_' . sha1(static::class);
}
Guard クラス(Guard オブジェクト)の仕事がざっくり理解できた気がします!
Provider って何なのじゃ?
guard
設定で呼ばれていた Provider は、以下のように定義します。
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Entities\User::class,
],
],
driver には eloquent か database 、または UserProvider
を実装したカスタム UserProvider のドライバを指定できます。
アプリケーションで Eloquent を使っている場合は、 eloquent
を指定することがほとんどでしょうか😃
eloquent
の場合は model を、 database
の場合は table を併せて指定します。
Provider はどこから呼ばれるのじゃ?
『SessionGuard は何をしているのじゃ?』 で書いた通り Guard から $this->provider->hogehoge()
のようにインスタンスが参照されます。
このインスタンスはどこで作られているのでしょう?
SessionGuard
の場合、 AuthManager
の resolve()
から、上記のメソッドが呼び出されます。
public function createSessionDriver($name, $config)
{
$provider = $this->createUserProvider($config['provider'] ?? null);
$guard = new SessionGuard($name, $provider, $this->app['session.store']);
// 略
}
この createUserProvider()
内で Provider を new して、 Guard のコンストラクタに渡しているようです。
そういえばカスタムGuard の登録時も、 Guard のコンストラクタに createUserProvider()
を渡していましたね😃
EloquentUserProvider は何をしているのじゃ?
今度は、使用頻度の高そうな EloquentUserProvider
の中身を見ていきます🙌
ログインしようとしているユーザーを取得する
SessionGuard
の attempt()
から呼び出されていた retrieveByCredentials()
を見てみましょう。
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials) ||
(count($credentials) === 1 &&
array_key_exists('password', $credentials))) {
return;
}
$query = $this->newModelQuery();
foreach ($credentials as $key => $value) {
if (Str::contains($key, 'password')) {
continue;
}
if (is_array($value) || $value instanceof Arrayable) {
$query->whereIn($key, $value);
} else {
$query->where($key, $value);
}
}
return $query->first();
}
ふむふむ。
パスワードを除いた Credentials (メールアドレスやユーザーID)でユーザーを絞り込んでますね。
newModelQuery()
では model
で指定された モデル (User等) を new し、その Query オブジェクト返しています。
protected function newModelQuery($model = null)
{
return is_null($model)
? $this->createModel()->newQuery()
: $model->newQuery();
}
public function createModel()
{
$class = '\\'.ltrim($this->model, '\\');
return new $class;
}
パスワードを検証する
retrieveByCredentials()
ではパスワードが検索対象から除外されていましたね。
パスワード検証には別途、 validateCredentials
が用意されています。
public function validateCredentials(UserContract $user, array $credentials)
{
$plain = $credentials['password'];
return $this->hasher->check($plain, $user->getAuthPassword());
}
IDからユーザーを取得する
Guard の user()
等から呼ばれます。
シンプルな検索ですね😃
public function retrieveById($identifier)
{
$model = $this->createModel();
return $this->newModelQuery($model)
->where($model->getAuthIdentifierName(), $identifier)
->first();
}
まとめ
やっと、Laravelの認証機能の全容(の一部)が見えてきました!やったぜ!