Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

16. UserProvider、Guard 和 Authenticatable 机制 #17

Open
xiaohuilam opened this issue Oct 5, 2018 · 0 comments
Open

16. UserProvider、Guard 和 Authenticatable 机制 #17

xiaohuilam opened this issue Oct 5, 2018 · 0 comments
Labels
book The digital book for laravel learning

Comments

@xiaohuilam
Copy link
Owner

xiaohuilam commented Oct 5, 2018

Laravel 提供了完善的用户状态判断鉴定、验证机制,这篇文章我们用十五分钟左右的时间来深入框架内部来了解它。
在学习之前,建议先查阅 Laravel China 文档库 > Laravel 5.7 > 用户认证,熟悉用法后,学习效率更高。

相关类

描述
Illuminate\Auth\SessionGuard guard 定义为 web 生效
Illuminate\Auth\EloquentUserProvider 默认的用户提供者
App\Http\Middleware\Authenticate 中间件
App\User 应用自定义 User 模型
Illuminate\Auth\Authenticatable 这是一个 trait,被 App\User 的父类 Illuminate\Foundation\Auth\User 所使用

配置文件解析

Auth::guard 可用的守护者定义在:

laravel/config/auth.php

Lines 38 to 48 in 4d76a68

'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],

这些配置文件中的 providers 定义在

laravel/config/auth.php

Lines 67 to 77 in 4d76a68

'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],


'driver' => 'session',
'driver' => 'token',

sessiontoken 分别指的是 Illuminate\Auth\SessionGuardIlluminate\Auth\TokenGuard

具体的构建方法见 AuthManager::createSessionDriverAuthManager::createTokenDriver,这两个方法是在 AuthManager::resolve 时用以下代码调用的。

$driverMethod = 'create'.ucfirst($config['driver']).'Driver';

注册流程 基础 + 容器部分

Illuminate\Auth\AuthManager

class AuthManager implements FactoryContract
{
use CreatesUserProviders;

用到了 Illuminate\Auth\CreatesUserProviders trait
此 trait 提供如下方法

  • public createUserProvider($provider = null): Illuminate\Contracts\Auth\UserProvider|null
  • protected getProviderConfiguration($provider): array|null
  • protected createDatabaseProvider($config): Illuminate\Auth\DatabaseUserProvider
  • protected createEloquentProvider($config): Illuminate\Auth\EloquentUserProvider
  • public getDefaultUserProvider(): string

Illuminate\Foundation\ApplicationregisterCoreContainerAliases 方法中,注册 auth 时, Illuminate\Auth\AuthManager 被使用。

public function registerCoreContainerAliases()
{
foreach ([
'app' => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class],
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
'cookie' => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
'db' => [\Illuminate\Database\DatabaseManager::class],
'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
'files' => [\Illuminate\Filesystem\Filesystem::class],
'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class],
'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class],
'hash' => [\Illuminate\Hashing\HashManager::class],
'hash.driver' => [\Illuminate\Contracts\Hashing\Hasher::class],
'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
'log' => [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class],
'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
'auth.password' => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
'queue.connection' => [\Illuminate\Contracts\Queue\Queue::class],
'queue.failer' => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
'redirect' => [\Illuminate\Routing\Redirector::class],
'redis' => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
'request' => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
'router' => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
'session' => [\Illuminate\Session\SessionManager::class],
'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
'url' => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}

容器对象的 registerCoreContainerAliases 方法是在 __construct 阶段被触发的。

注册流程 服务提供者 Illuminate\Auth\AuthServiceProvider

Illuminate\Auth\AuthServiceProvider 中的 register 方法,

public function register()
{
$this->registerAuthenticator();
$this->registerUserResolver();
$this->registerAccessGate();
$this->registerRequestRebindHandler();
}

registerAuthenticator 方法为

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();
});
}

registerUserResolver 方法为

protected function registerUserResolver()
{
$this->app->bind(
AuthenticatableContract::class, function ($app) {
return call_user_func($app['auth']->userResolver());
}
);
}

这里便将 authIlluminate\Contracts\Auth\Authenticatable 成功的从 Application 中的 alias 升级为 singleton 。

在我们的 config/app.phpproviders 第一个便是 Illuminate\Auth\AuthServiceProvider

laravel/config/app.php

Lines 122 to 127 in 4d76a68

'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,

到这里,web 环境框架启动部分所有初始化用户状态维护机制就完成。

中间件流程 限制登陆后可访问

当一个路由声明必须登陆后可访问,也就是 auth 中间件,会触发:

public function __construct(Auth $auth)
{
$this->auth = $auth;
}

触发容器构建 AuthManager

在容器构建 AuthManager 时,会触发 __construct

public function __construct($app)
{
$this->app = $app;
$this->userResolver = function ($guard = null) {
return $this->guard($guard)->user();
};
}

中间件的 handle

public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($request, $guards);
return $next($request);
}

调用了中间件 authenticate 方法

protected function authenticate($request, array $guards)
{
if (empty($guards)) {
$guards = [null];
}
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
}
throw new AuthenticationException(
'Unauthenticated.', $guards, $this->redirectTo($request)
);
}

如果没登陆,报错

throw new AuthenticationException(
'Unauthenticated.', $guards, $this->redirectTo($request)
);

获取当前用户流程

如果手工在业务中使用 auth()->user() 会触发

public function __call($method, $parameters)
{
return $this->guard()->{$method}(...$parameters);
}

AuthManagerguard() 默认返回的是 SessionGuard,所以 auth()->user() 调用的是

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;
}

其 131 行的 $this->provider->retrieveById($id) 调用了 EloquentUserProvider::retrieveById

public function retrieveById($identifier)
{
$model = $this->createModel();
return $model->newQuery()
->where($model->getAuthIdentifierName(), $identifier)
->first();
}

$this->createModel() 返回的是 $this->model,是在

public function __construct(HasherContract $hasher, $model)
{
$this->model = $model;
$this->hasher = $hasher;
}
设置进去的。

AuthManager 调用 createUserProvider 后调用 createEloquentProvider 方法时,

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."
);
}

protected function createEloquentProvider($config)
{
return new EloquentUserProvider($this->app['hash'], $config['model']);
}

其传进去的 $config 的获取逻辑为

protected function getProviderConfiguration($provider)
{
if ($provider = $provider ?: $this->getDefaultUserProvider()) {
return $this->app['config']['auth.providers.'.$provider];
}
}

也就是这里设定的 'model' => App\User::class

laravel/config/auth.php

Lines 67 to 77 in 4d76a68

'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],

前面 getAuthIdentifierName 的方法,就是调用 App\User::getAuthIdentifierName 来查找用户了。

return $model->newQuery()
->where($model->getAuthIdentifierName(), $identifier)
->first();


本文章采用「署名 4.0 国际」创作共享协议,
转载前请阅读 相关说明 »

@xiaohuilam xiaohuilam changed the title UserProvider 和 Guard 机制 UserProvider 和 Guard 机制 [0%] Oct 5, 2018
@xiaohuilam xiaohuilam changed the title UserProvider 和 Guard 机制 [0%] UserProvider、Guard 和 Authenticatable 机制 [0%] Oct 5, 2018
@xiaohuilam xiaohuilam changed the title UserProvider、Guard 和 Authenticatable 机制 [0%] UserProvider、Guard 和 Authenticatable 机制 Oct 5, 2018
@xiaohuilam xiaohuilam added the book The digital book for laravel learning label Oct 5, 2018
@xiaohuilam xiaohuilam changed the title UserProvider、Guard 和 Authenticatable 机制 16. UserProvider、Guard 和 Authenticatable 机制 Oct 5, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
book The digital book for laravel learning
Projects
None yet
Development

No branches or pull requests

1 participant