Laravel
laravel

Laravel の Guard(認証) って実際何をやっているのじゃ?

https://qiita.com/tomoeine/items/40a966bf3801633cf90f

最終更新日 2019年09月06日投稿日 2019年09月06日

認証周りが絡む特殊な実装をしようとしたときに、色々と調べてみたのでせっかくだしまとめます☺️

Guard の使い方等は 「認証」ドキュメントをご参考ください。
[Laravel 5.8 認証]
(https://readouble.com/laravel/5.8/ja/authentication.html)

Guardってどうやって使うんだったっけ?(おさらい)

config/auth.php

    'guards' => [
        'guard-name' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
    ],

こんな風に guards 設定しておいて、

route/web.php

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() に渡します。

AuthServiceProvider.php

    public function boot()
    {
        $this->registerPolicies();

        Auth::extend('jwt', function($app, $name, array $config) {
            return new JwtGuard(Auth::createUserProvider($config['provider']));
        });
    }

ここで呼び出されている Auth::extend() では、以下のように driver を登録しています。

AuthManager.php

    public function extend($driver, Closure $callback)
    {
        $this->customCreators[$driver] = $callback;

        return $this;
    }

認証時等に同クラスの callCustomCreator() が呼び出され、登録した Guard インスタンス を返します。

AuthManager.php


    protected function callCustomCreator($name, array $config)
    {
        return $this->customCreators[$config['driver']]($this->app, $name, $config);
    }

SessionGuard や TokenGuard はどのように呼ばれるのじゃ?

デフォルトで用意されている session と guard 用には createSessionDriver() createTokenDriver() が AuthManager 内に用意されており、この中で Guard クラスから new していました😃

AuthManager.php

    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 (ログインを試みる)

SessionGuard.php

    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 で呼ぶこともありますね。

SessionGuard.php

    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 (ログイン済ユーザーを取得する)

SessionGuard.php

    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 に渡し、ユーザーオブジェクトを取得・返却しています。

なおセッションには以下のキーで保持されているようです。

SessionGuard.php

    public function getName()
    {
        return 'login_' . $this->name . '_' . sha1(static::class);
    }

Guard クラス(Guard オブジェクト)の仕事がざっくり理解できた気がします!

Provider って何なのじゃ?

guard 設定で呼ばれていた Provider は、以下のように定義します。

auth.php

    '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() から、上記のメソッドが呼び出されます。

AuthManager.php

    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() を見てみましょう。

EloquentUserProvider.php

   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 オブジェクト返しています。

EloquentUserProvider.php

    protected function newModelQuery($model = null)
    {
        return is_null($model)
                ? $this->createModel()->newQuery()
                : $model->newQuery();
    }

EloquentUserProvider.php

    public function createModel()
    {
        $class = '\\'.ltrim($this->model, '\\');

        return new $class;
    }

パスワードを検証する

retrieveByCredentials() ではパスワードが検索対象から除外されていましたね。
パスワード検証には別途、 validateCredentials が用意されています。

EloquentUserProvider.php

    public function validateCredentials(UserContract $user, array $credentials)
    {
        $plain = $credentials['password'];

        return $this->hasher->check($plain, $user->getAuthPassword());
    }

IDからユーザーを取得する

Guard の user() 等から呼ばれます。
シンプルな検索ですね😃

EloquentUserProvider.php

    public function retrieveById($identifier)
    {
        $model = $this->createModel();

        return $this->newModelQuery($model)
                    ->where($model->getAuthIdentifierName(), $identifier)
                    ->first();
    }

まとめ

やっと、Laravelの認証機能の全容(の一部)が見えてきました!やったぜ!

コメントを残す

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