laravelのauth処理を追う。
HomeControllerのコンストラクタでauth middlewareが指定されている想定。
class HomeController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
auth middlewareの実態はkernelに記載されているはず。
見てみるとAuthenticate(=認証)クラスが指定されていた。
認証はユーザーが誰であるかを判断する仕組みのこと
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
Authenticateのロジックを見ていく。
middlewareは実行時にhandle関数が呼ばれる。第3引数以降はミドルウェア独自に指定できる引数になる。
Authミドルウェアはguardを引数で渡せるようだ。↓きっとこういう感じで渡すのだろう。複数guardを用意して適宜guardを指定する仕組み。
$this->middleware('auth:guard名');
public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($guards);
return $next($request);
}
authenticate関数を見ていく。
guardが空の場合はauthのauthenticate関数を実行している。
guard要素がある場合はauthを使ってguardインスタンスを取得し、check関数を実行している。checkが通ればshouldUseするのか。
いずれも通らなければAuthenticationExceptionが発生してユーザーにエラーが表示される。
protected function authenticate(array $guards)
{
if (empty($guards)) {
return $this->auth->authenticate();
}
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
}
throw new AuthenticationException('Unauthenticated.', $guards);
}
Authenticateのauthはコンストラクタで渡される。\Illuminate\Contracts\Auth\Factoryが渡されるようだ。
middlewareはサービスコンテナによってインスタンス化されるため、これの実態が何かはパッとは分からない。
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
サービスコンテナにinstanceをbindしている箇所は限られている。以下のどこかに定義があると思う。
・confog/app.php
・ServiceProvider
・Kernelのbootstrap
config/app.phpのprovidersで指定されているIlluminate\Auth\AuthServiceProvider::class
にbind処理が書いてあった。
authの実態はAuthManager
だ。こいつがguardのインスタンスを返したり認証チェックを行ったりする。
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();
});
}
AuthManagerを見てみる。
先ほど使用されたguard関数, shouldUse関数, authenticate関数を追う。
まずはguard関数。引数でもらったguard名もしくはdefault名を使ってGuardインスタンスを返すようだ。
今までguardと読んでいたものはここではdriverと言われるのだろうか。
/**
* Attempt to get the guard from the local cache.
*
* @param string $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);
}
shouldUse関数をみる。デフォルトのdriverを指定できるようにする関数のようだ。
routeやcontrollerで複数guardを指定した時にどれをデフォルトdriverにするか決めるのに指定していたのか。
/**
* Set the default guard driver the factory should serve.
*
* @param string $name
* @return void
*/
public function shouldUse($name)
{
$name = $name ?: $this->getDefaultDriver();
$this->setDefaultDriver($name);
$this->userResolver = function ($name = null) {
return $this->guard($name)->user();
};
}
authenticate関数を追う。が、AuthManagerにはこの関数は存在しない。
__callマジックメソッドで代わりにguardの関数を実行しているようだ。
/**
* Dynamically call the default driver instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->guard()->{$method}(...$parameters);
}
もう少しguardの実態を追っていく。
authミドルウェアで使用されるguardはrouteやcontrollerでauthを指定する時に一緒に指定できる。
HomeControllerではguard未指定なのでdefaultが使用されるようだ。
public function __construct()
{
$this->middleware('auth');
}
default guardはgetDefaultDriver関数で決められる。
config/auth.defaults.guardに定義されているようだ。見てみるとwebというのが指定されている。
web・・・?
public function getDefaultDriver()
{
return $this->app['config']['auth.defaults.guard'];
}
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
guardの実態はAuthManagerのresolve関数で得られるようだ。
defaultでweb
が指定されていたので$name = 'web'になる。
configを取得し、customCreatorならcallCustomCreatorを実行、そうじゃないならcreateOOODriver関数を文字列から作成して実行するようだ。
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を追う。config/auth.guards.webに指定されているようだ。
webのguardではsessionとproviderが設定されていた。
/**
* Get the guard configuration.
*
* @param string $name
* @return array
*/
protected function getConfig($name)
{
return $this->app['config']["auth.guards.{$name}"];
}
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
つまりwebのguardではcreateSessionDriver
という関数名を作成し、sessionベースの認証を行うguardインスタンスを作成するということなのか。
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
だんだんguardの実態が分かってきた。createSessionDriverを追っていく。
まずcreateUserProviderでprovider?を作成するようだ。webのconfig['proivder']はusers
が指定されていた。なんやかんやあってEloquentUserProvider.php
が作成され、それを元にSessionGuard.php
が作成される。
長かったけどHomeControllerで使用されるguardはSessionGuard
インスタンスのようだ。なるほど。
/**
* 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;
}
authミドルウェアの実装に戻るとguardのauthenticate関数が呼ばれていた。SessionGuardのauthenticate関数を追う。
見てみるとSessionGuardでuseしているGuardHelperで定義され、内部でSessionGuardのuser関数を読んでいるようだ。
protected function authenticate(array $guards)
{
if (empty($guards)) {
return $this->auth->authenticate(); //実際はguardのauthenticate関数呼び出し
}
class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
use GuardHelpers, Macroable;
public function authenticate()
{
if (! is_null($user = $this->user())) {
return $user;
}
throw new AuthenticationException;
}
SessionGuardのuser関数を追う。
ここでは現在認証済みのuser
を返すようだ。sessionからuserを取得する。
/**
* Get the currently authenticated user.
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
if ($this->loggedOut) {
return;
}
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if (! is_null($this->user)) {
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
$this->fireAuthenticatedEvent($this->user);
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$recaller = $this->recaller();
if (is_null($this->user) && ! is_null($recaller)) {
$this->user = $this->userFromRecaller($recaller);
if ($this->user) {
$this->updateSession($this->user->getAuthIdentifier());
$this->fireLoginEvent($this->user, true);
}
}
return $this->user;
}
あとはauthミドルウェアに処理が戻り、sessionGuardでreturnしたが使われることもなく、$nextが実行され次のmiddlewareの処理が進む。
public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($guards);
return $next($request);
}
あれ・・・認証されていないとリダイレクトされるはずだけどどこでやってるんだろう。
後ほど調べる。