前回の続きです。
今回は、/loginをGETで呼び出したときを見ていきます。
/loginをGETで呼び出すと、Auth\LoginControllerのshowLoginFormが実行されます。
なお、showLoginFormメソッドはAuth\LoginControllerでuseしているIlluminate\Foundation\Auth\AuthenticatesUsersに定義されています。
/** * Show the application's login form. * * @return \Illuminate\Http\Response */ public function showLoginForm() { return view('auth.login'); }
追加された、resources/views/auth/login.blade.phpが表示されます。
続いて、/loginをPOSTで呼び出したときを見ていきます。
ちなみにloginの処理はやや複雑です。
/loginをPOSTで呼び出すと、Auth\LoginControllerのloginが実行されます。
login()メソッドもAuth\LoginControllerでuseしているIlluminate\Foundation\Auth\AuthenticatesUsersに定義されています。
コードを見てみましょう。
/** * Handle a login request to the application. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse * * @throws \Illuminate\Validation\ValidationException */ public function login(Request $request) { $this->validateLogin($request); // If the class is using the ThrottlesLogins trait, we can automatically throttle // the login attempts for this application. We'll key this by the username and // the IP address of the client making these requests into this application. if (method_exists($this, 'hasTooManyLoginAttempts') && $this->hasTooManyLoginAttempts($request)) { $this->fireLockoutEvent($request); return $this->sendLockoutResponse($request); } if ($this->attemptLogin($request)) { return $this->sendLoginResponse($request); } // If the login attempt was unsuccessful we will increment the number of attempts // to login and redirect the user back to the login form. Of course, when this // user surpasses their maximum number of attempts they will get locked out. $this->incrementLoginAttempts($request); return $this->sendFailedLoginResponse($request); }
validateLoginの部分はPOSTされたデータのバリデーションを行なっています。
if文のところはログイン試行回数チェックを行い、設定したログイン試行回数を超えた場合にアカウントをロックさせる処理が記載されてます。
アカウントのロックは、sendLockoutResponseメソッドで実行されます。sendLockoutResponseメソッドは、Illuminate/Foundation/Auth/ThrottlesLogins.phpに定義されています。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Foundation/Auth/ThrottlesLogins.php#L41
sendLockoutResponseメソッドの実行を判断しているhasTooManyLoginAttemptsメソッドも上記と同様にThrottlesLoginsトレイトに定義されてます。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Foundation/Auth/ThrottlesLogins.php#L15
ちなみにデフォルトだとログイン試行回数は5回、ロックタイムは1分に設定されてます。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Foundation/Auth/ThrottlesLogins.php#L106
次のif文がメイン部分です。
attemptLoginメソッドでログインの可否を判定しています。
attemptLoginはIlluminate/Foundation/Auth/AuthenticatesUsers.phpで定義されています。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php#L73
コードを確認しましょう
/** * Attempt to log the user into the application. * * @param \Illuminate\Http\Request $request * @return bool */ protected function attemptLogin(Request $request) { return $this->guard()->attempt( $this->credentials($request), $request->filled('remember') ); }
guard()メソッドを確認します。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php#L176
/** * Get the guard to be used during authentication. * * @return \Illuminate\Contracts\Auth\StatefulGuard */ protected function guard() { return Auth::guard(); }
Authファサードのguard()メソッドを実行しています。
AuthファサードのgetFacadeAccessor()メソッドを確認すると「return ‘auth’;」とされています。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Support/Facades/Auth.php#L39
config/app.phpをみると、Illuminate/Auth/AuthServiceProvider.phpがサービスプロバイダとして登録されており、AuthServiceProviderのregister()メソッドを確認すると、その中でさらにregisterAuthenticator()メソッドが実行されています。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Auth/AuthServiceProvider.php#L12
/** * Register the service provider. * * @return void */ public function register() { $this->registerAuthenticator(); $this->registerUserResolver(); $this->registerAccessGate(); $this->registerRequestRebindHandler(); $this->registerEventRebindHandler(); } /** * Register the authenticator services. * * @return void */ protected function registerAuthenticator() { $this->app->singleton('auth', function ($app) { // Once the authentication service has actually been requested by the developer // we will set a variable in the application indicating such. This helps us // know that we need to set any queued cookies in the after event later. $app['auth.loaded'] = true; return new AuthManager($app); }); $this->app->singleton('auth.driver', function ($app) { return $app['auth']->guard(); }); }
これで「auth」をキーとして、AuthManagerのインスタンスが返されることがわかりました。
続いて、AuthManagerの中で定義されているguard()メソッドを確認しましょう
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Auth/AuthManager.php#L58
/** * Attempt to get the guard from the local cache. * * @param string|null $name * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard */ public function guard($name = null) { $name = $name ?: $this->getDefaultDriver(); return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name); }
まず、$nameはnullなので、getDefaultDriver()メソッドが実行されています。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Auth/AuthManager.php#L181
/** * Get the default authentication driver name. * * @return string */ public function getDefaultDriver() { return $this->app['config']['auth.defaults.guard']; }
cofig/auth.phpを確認すると下記のようになっているので、ここでは「web」がreturnされます。
'defaults' => [ 'guard' => 'web', 'passwords' => 'users', ],
続いて、AuthManagerのresolve()メソッドをみてみましょう。
このresolve()メソッドに「web」が渡されます。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Auth/AuthManager.php#L71
/** * Resolve the given guard. * * @param string $name * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard * * @throws \InvalidArgumentException */ protected function resolve($name) { $config = $this->getConfig($name); if (is_null($config)) { throw new InvalidArgumentException("Auth guard [{$name}] is not defined."); } if (isset($this->customCreators[$config['driver']])) { return $this->callCustomCreator($name, $config); } $driverMethod = 'create'.ucfirst($config['driver']).'Driver'; if (method_exists($this, $driverMethod)) { return $this->{$driverMethod}($name, $config); } throw new InvalidArgumentException( "Auth driver [{$config['driver']}] for guard [{$name}] is not defined." ); }
まずは、getConfig()メソッドを確認します。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Auth/AuthManager.php#L170
/** * Get the guard configuration. * * @param string $name * @return array */ protected function getConfig($name) { return $this->app['config']["auth.guards.{$name}"]; }
$nameには「web」が入っているので、config/auth.phpのguardsのwebを確認します。
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'token', 'provider' => 'users', 'hash' => false, ], ],
これで、[‘driver’ => ‘session’, ‘provider’ => ‘users’]という配列がresolve()メソッドの$configに格納されることがわかります。
カスタムGuardを登録していない場合は、次のif文は実行されないので、$driverMethodに「createSessionDriver」という文字列が格納されます。
続いて、if文でcreateSessionDriver()メソッドの存在確認がされ、存在する場合、そのままcreateSessionDriver()メソッドが実行されます。
createSessionDriver()メソッドは、AuthManagerで定義されているので確認しましょう。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Auth/AuthManager.php#L114
/** * Create a session based authentication guard. * * @param string $name * @param array $config * @return \Illuminate\Auth\SessionGuard */ public function createSessionDriver($name, $config) { $provider = $this->createUserProvider($config['provider'] ?? null); $guard = new SessionGuard($name, $provider, $this->app['session.store']); // When using the remember me functionality of the authentication services we // will need to be set the encryption instance of the guard, which allows // secure, encrypted cookie values to get generated for those cookies. if (method_exists($guard, 'setCookieJar')) { $guard->setCookieJar($this->app['cookie']); } if (method_exists($guard, 'setDispatcher')) { $guard->setDispatcher($this->app['events']); } if (method_exists($guard, 'setRequest')) { $guard->setRequest($this->app->refresh('request', $guard, 'setRequest')); } return $guard; }
まずは、createUserProvider()メソッドを確認します。
これは、AuthManagerがuseしているIlluminate/Auth/CreatesUserProvidersに定義されています。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Auth/CreatesUserProviders.php#L106
/** * Create the user provider implementation for the driver. * * @param string|null $provider * @return \Illuminate\Contracts\Auth\UserProvider|null * * @throws \InvalidArgumentException */ public function createUserProvider($provider = null) { if (is_null($config = $this->getProviderConfiguration($provider))) { return; } if (isset($this->customProviderCreators[$driver = ($config['driver'] ?? null)])) { return call_user_func( $this->customProviderCreators[$driver], $this->app, $config ); } switch ($driver) { case 'database': return $this->createDatabaseProvider($config); case 'eloquent': return $this->createEloquentProvider($config); default: throw new InvalidArgumentException( "Authentication user provider [{$driver}] is not defined." ); } }
まず、今回の場合$providerには「user」が格納されているので、
最初のif文の中のgetProviderConfigration()メソッドの引数には「user」が入っていることになります。
/** * Get the user provider configuration. * * @param string|null $provider * @return array|null */ protected function getProviderConfiguration($provider) { if ($provider = $provider ?: $this->getDefaultUserProvider()) { return $this->app['config']['auth.providers.'.$provider]; } }
すると、config/auth.phpのprovidersのuserを見ればいいことになる
'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\User::class, ], ],
これで、[‘driver’ => ‘eloquent’, ‘model’ => ‘App\User::class’]という配列が$configに代入される。
そして、続くif文の中で、$driverに「eloquent」が格納されることになり、
switch文でEloquentUserProviderがreturnされるということになるわけです。
長くなったので、再度、createSessionDriver()メソッドを確認します。
/** * Create a session based authentication guard. * * @param string $name * @param array $config * @return \Illuminate\Auth\SessionGuard */ public function createSessionDriver($name, $config) { $provider = $this->createUserProvider($config['provider'] ?? null); $guard = new SessionGuard($name, $provider, $this->app['session.store']); // When using the remember me functionality of the authentication services we // will need to be set the encryption instance of the guard, which allows // secure, encrypted cookie values to get generated for those cookies. if (method_exists($guard, 'setCookieJar')) { $guard->setCookieJar($this->app['cookie']); } if (method_exists($guard, 'setDispatcher')) { $guard->setDispatcher($this->app['events']); } if (method_exists($guard, 'setRequest')) { $guard->setRequest($this->app->refresh('request', $guard, 'setRequest')); } return $guard; }
さて、話をまとめると、createUserProvider()メソッドにより、$providerにはEloquentUserProviderが格納されていることがわかります。
そして、続く、「new SessionGuard($name, $provider, $this->app[‘session.store’]);が実行され、$guardにSessionGuardインスタンスが格納されます。
なお、$nameには、「web」という文字列
$providerには、EloquentUserProviderインスタンスが格納されています。
SessionGuardのコンストラクタをみておきましょう。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Auth/SessionGuard.php#L88
/** * Create a new authentication guard. * * @param string $name * @param \Illuminate\Contracts\Auth\UserProvider $provider * @param \Illuminate\Contracts\Session\Session $session * @param \Symfony\Component\HttpFoundation\Request|null $request * @return void */ public function __construct($name, UserProvider $provider, Session $session, Request $request = null) { $this->name = $name; $this->session = $session; $this->request = $request; $this->provider = $provider; }
話を「Illuminate/Foundation/Auth/AuthenticatesUsers.php」のattemptLogin()メソッドに戻します。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php#L73
/** * Attempt to log the user into the application. * * @param \Illuminate\Http\Request $request * @return bool */ protected function attemptLogin(Request $request) { return $this->guard()->attempt( $this->credentials($request), $request->filled('remember') ); }
つまり、ここでのguard()はSessionGuardインスタンスのことだったのです。
では、SessionGuardインスタンスのattempt()メソッドを確認しましょう。
なお、$credentialsには、usernameとpasswordの配列が格納されています。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Auth/SessionGuard.php#L338
/** * Attempt to authenticate a user using the given credentials. * * @param array $credentials * @param bool $remember * @return bool */ public function attempt(array $credentials = [], $remember = false) { $this->fireAttemptEvent($credentials, $remember); $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); // If an implementation of UserInterface was returned, we'll ask the provider // to validate the user against the given credentials, and if they are in // fact valid we'll log the users into the application and return true. if ($this->hasValidCredentials($user, $credentials)) { $this->login($user, $remember); return true; } // If the authentication attempt fails we will fire an event so that the user // may be notified of any suspicious attempts to access their account from // an unrecognized user. A developer may listen to this event as needed. $this->fireFailedEvent($user, $credentials); return false; }
まずは、fireAttemptEvent()メソッドでログイン試行イベントを発生させます。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Auth/SessionGuard.php#L602
/** * Fire the attempt event with the arguments. * * @param array $credentials * @param bool $remember * @return void */ protected function fireAttemptEvent(array $credentials, $remember = false) { if (isset($this->events)) { $this->events->dispatch(new Events\Attempting( $this->name, $credentials, $remember )); } }
続いて、retrieveByCredentials()メソッドを確認します。
retrieveByCredentials()メソッドは$this->providerから呼ばれていますが、
ここでのproviderは、EloquentUserProviderインスタンスのことなので、EloquentUserProviderのretrieveByCredentials()メソッドを確認します。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Auth/EloquentUserProvider.php#L100
/** * Retrieve a user by the given credentials. * * @param array $credentials * @return \Illuminate\Contracts\Auth\Authenticatable|null */ public function retrieveByCredentials(array $credentials) { if (empty($credentials) || (count($credentials) === 1 && array_key_exists('password', $credentials))) { return; } // First we will add each credential element to the query as a where clause. // Then we can execute the query and, if we found a user, return it in a // Eloquent User "model" that will be utilized by the Guard instances. $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(); }
ここでは、ログインを試行するユーザーをDBから取得しています。
続いて、if文の中のhasValidCredentials()メソッドが実行されます。hasValidCredentials()の中身をみると
$this->provider->validateCredentials()となっているので、EloquentUserProviderインスタンスのvalidateCredentials()メソッドを確認します。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Auth/SessionGuard.php#L368
/** * Determine if the user matches the credentials. * * @param mixed $user * @param array $credentials * @return bool */ protected function hasValidCredentials($user, $credentials) { return ! is_null($user) && $this->provider->validateCredentials($user, $credentials); }
EloquentUserProviderインスタンスのvalidateCredentials()メソッドをみてみましょう。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Auth/EloquentUserProvider.php#L134
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials
* @return bool
*/
public function validateCredentials(UserContract $user, array $credentials)
{
$plain = $credentials[‘password’];
return $this->hasher->check($plain, $user->getAuthPassword());
}
POSTで送信されたパスワードとDBから取得してきたパスワードを比較しています。
一致していれば、if文の中の「$this->login($user, $remember);」が実行されることになります。
SessionGuardのlogin()メソッドを確認しましょう。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Auth/SessionGuard.php#L398
/** * Log a user into the application. * * @param \Illuminate\Contracts\Auth\Authenticatable $user * @param bool $remember * @return void */ public function login(AuthenticatableContract $user, $remember = false) { $this->updateSession($user->getAuthIdentifier()); // If the user should be permanently "remembered" by the application we will // queue a permanent cookie that contains the encrypted copy of the user // identifier. We will then decrypt this later to retrieve the users. if ($remember) { $this->ensureRememberTokenIsSet($user); $this->queueRecallerCookie($user); } // If we have an event dispatcher instance set we will fire an event so that // any listeners will hook into the authentication events and run actions // based on the login and logout events fired from the guard instances. $this->fireLoginEvent($user, $remember); $this->setUser($user); }
login()メソッドでは、まず、updateSession()メソッドで、セッションにユーザーIDを格納しています。
続いて、remember_tokenをensureRememberTokenIsSet()メソッドで、DBに格納しています。
最後にfireLoginEventでログインイベントを発生させています。
長々と書いてきましたが、そろそろ終わりにしたいと思います。
ここで、Illuminate\Foundation\Auth\AuthenticatesUsersのlogin()メソッドに戻ります。
※コメントを省きました
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php#L23
/** * Handle a login request to the application. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse * * @throws \Illuminate\Validation\ValidationException */ public function login(Request $request) { $this->validateLogin($request); if (method_exists($this, 'hasTooManyLoginAttempts') && $this->hasTooManyLoginAttempts($request)) { $this->fireLockoutEvent($request); return $this->sendLockoutResponse($request); } if ($this->attemptLogin($request)) { return $this->sendLoginResponse($request); } $this->incrementLoginAttempts($request); return $this->sendFailedLoginResponse($request); }
仮にログインが成功すると「$this->attemptLogin($request)」はtrueになりますので、最後は「return $this->sendLoginResponse($request);」が実行されることになります。
sendLoginResponse()メソッドを確認しましょう。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php#L97
/** * Send the response after the user was authenticated. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ protected function sendLoginResponse(Request $request) { $request->session()->regenerate(); $this->clearLoginAttempts($request); return $this->authenticated($request, $this->guard()->user()) ?: redirect()->intended($this->redirectPath()); }
まずは、セッションIDを再生成しています。
次に、clearLoginAttempts()メソッドでログインロックをクリアします。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Foundation/Auth/ThrottlesLogins.php#L63
最後にデフォルトの状態であれば、/homeへリダイレクトされて、ログインが完了します。
なお、authenticated()メソッドの中身は空なので、ログイン処理のリダイレクト直前に処理を追加した場合は、Auth\LoginController等で実装します。
redirectPath()メソッドの中身を確認しておきます。
https://github.com/laravel/framework/blob/v6.4.0/src/Illuminate/Foundation/Auth/RedirectsUsers.php#L7
長い記事になりましたが、今回は以上です。