LoginSignup
271
254

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-09-06

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

Guard の使い方等は 「認証」ドキュメントをご参考ください。
Laravel 5.8 認証

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 はどのように呼ばれるのじゃ?

デフォルトで用意されている sessionguard 用には 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 には eloquentdatabase 、または UserProvider を実装したカスタム UserProvider のドライバを指定できます。
アプリケーションで Eloquent を使っている場合は、 eloquent を指定することがほとんどでしょうか😃
eloquent の場合は model を、 database の場合は table を併せて指定します。

Provider はどこから呼ばれるのじゃ?

SessionGuard は何をしているのじゃ?』 で書いた通り Guard から $this->provider->hogehoge() のようにインスタンスが参照されます。
このインスタンスはどこで作られているのでしょう?

SessionGuard の場合、 AuthManagerresolve() から、上記のメソッドが呼び出されます。

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 の中身を見ていきます🙌

ログインしようとしているユーザーを取得する

SessionGuardattempt() から呼び出されていた 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の認証機能の全容(の一部)が見えてきました!やったぜ!

271
254
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
271
254