laravel5.5用户认证源码分析

Posted ArcheWong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了laravel5.5用户认证源码分析相关的知识,希望对你有一定的参考价值。

[toc]

1. 生成相关文件和配置

快速生成命令

php artisan make:auth

运行后,使用git查看有哪些文件变化

$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   routes/web.php
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   app/Http/Controllers/HomeController.php
#   resources/views/auth/
#   resources/views/home.blade.php
#   resources/views/layouts/
no changes added to commit (use "git add" and/or "git commit -a")

2. 分析路由文件

查看下路由文件,发现多了两条路由信息

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

//以下为新加的路由
Auth::routes();

Route::get('/home', '[email protected]')->name('home');

分析下Auth::routes()做了什么事

定位到Auth这个Facades,config/app.php中的aliases中有一条

'aliases' => [
        'Auth' => Illuminate\Support\Facades\Auth::class,
        // ...其他
    ],

打开Illuminate\Support\Facades\Auth.php后看到有个routes()方法

    /**
     * Register the typical authentication routes for an application.
     *
     * @return void
     */
    public static function routes()
    {
        static::$app->make('router')->auth();
    }

定位到Illuminate\Routing\route.php,有个auth()方法,这个就是我们route\web.php路由文件中Autu::routes()最后能生成的路由。

    public function auth()
    {
        // Authentication Routes...
        $this->get('login', 'Auth\[email protected]')->name('login');
        $this->post('login', 'Auth\[email protected]');
        $this->post('logout', 'Auth\[email protected]')->name('logout');

        // Registration Routes...
        $this->get('register', 'Auth\[email protected]')->name('register');
        $this->post('register', 'Auth\[email protected]');

        // Password Reset Routes...
        $this->get('password/reset', 'Auth\[email protected]')->name('password.request');
        $this->post('password/email', 'Auth\[email protected]')->name('password.email');
        $this->get('password/reset/{token}', 'Auth\[email protected]')->name('password.reset');
        $this->post('password/reset', 'Auth\[email protected]');
    }

查看下生效的路由,和上面的对比下,相匹配

$ php artisan route:list
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
| Domain | Method   | URI                    | Name             | Action                                                                 | Middleware   |
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
|        | GET|HEAD | /                      |                  | Closure                                                                | web          |
|        | GET|HEAD | api/user               |                  | Closure                                                                | api,auth:api |
|        | GET|HEAD | home                   | home             | App\Http\Controllers\[email protected]                              | web,auth     |
|        | GET|HEAD | login                  | login            | App\Http\Controllers\Auth\[email protected]                | web,guest    |
|        | POST     | login                  |                  | App\Http\Controllers\Auth\[email protected]                        | web,guest    |
|        | POST     | logout                 | logout           | App\Http\Controllers\Auth\[email protected]                       | web          |
|        | POST     | password/email         | password.email   | App\Http\Controllers\Auth\[email protected]  | web,guest    |
|        | GET|HEAD | password/reset         | password.request | App\Http\Controllers\Auth\[email protected] | web,guest    |
|        | POST     | password/reset         |                  | App\Http\Controllers\Auth\[email protected]                | web,guest    |
|        | GET|HEAD | password/reset/{token} | password.reset   | App\Http\Controllers\Auth\[email protected]        | web,guest    |
|        | GET|HEAD | register               | register         | App\Http\Controllers\Auth\[email protected]      | web,guest    |
|        | POST     | register               |                  | App\Http\Controllers\Auth\[email protected]                  | web,guest    |
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+

3. 以登陆开始为例,分析auth到底是怎么工作的

3.1 分析登录文件

定位文件 app/Http/Controllers/Auth/LoginController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

# 1. 登录控制器
class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }
}

LoginController.php并没有登录方法,why?实际上这里使用了trait,如果你用过软删除应该记得

use SoftDeletes;

这里也是使用了trait

use AuthenticatesUsers;

打开Illuminate\Foundation\Auth\AuthenticatesUsers.php

<?php

namespace Illuminate\Foundation\Auth;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

trait AuthenticatesUsers
{
    use RedirectsUsers, ThrottlesLogins;


    /**
     * Handle a login request to the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse
     */
    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 ($this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        # 2. 这里调用了attemptLogin()
        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);
    }

    /**
     * Attempt to log the user into the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return bool
     */
    protected function attemptLogin(Request $request)
    {   
        # 3. 找到 guard()方法
        # 这里的attemp()方法一会分析
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }
    
    /**
     * Get the guard to be used during authentication.
     *
     * @return \Illuminate\Contracts\Auth\StatefulGuard
     */
    protected function guard()
    {
        # 4. 访问了Auth门面的guard()方法
        return Auth::guard();
    }
    
    // ...其它方法暂时省略

}

到这里我们知道login方法的结果 : 先实例化Auth::guard()实例,最终返回了这个实例的attempt()方法

3.2 分析门面Auth。

以下使用config/auth.php的原始默认配置进行分析

根据config/app.php里面的服务提供器和Facades, 最终定位到 Illuminate\Auth\AuthManager.php


    /**
     * 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)
    {
        # 5. 获取看守器,如果guard()没有参数则使用默认设置
        $name = $name ?: $this->getDefaultDriver();

        # 7. $name= 'web' 定位到resolve()方法
        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }

    /**
     * Resolve the given guard.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     *
     * @throws \InvalidArgumentException
     */
    protected function resolve($name)
    {
        #8. 获取配置
        $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);
        }

        # 10 .$config = ['driver' => 'session','provider' => 'users',]
        # 拼接创建看守器的方法名  这里默认是 createSessionDriver
        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this, $driverMethod)) {
            # 11. 访问createSessionDriver()
            return $this->{$driverMethod}($name, $config);
        }

        throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined.");
    }

    /**
     * Call a custom driver creator.
     *
     * @param  string  $name
     * @param  array  $config
     * @return mixed
     */
    protected function callCustomCreator($name, array $config)
    {
        return $this->customCreators[$config['driver']]($this->app, $name, $config);
    }

    /**
     * Create a session based authentication guard.
     *
     * @param  string  $name
     * @param  array  $config
     * @return \Illuminate\Auth\SessionGuard
     */
    public function createSessionDriver($name, $config)
    {
        # $name = 'session'
        # $cofnig['provider'] = 'users'
        
        # 12. createUserProvider不在此文件,定位到最上面有使用trait
        # 通过这句 use CreatesUserProviders; 定位
        # 查看下面的Illuminate\Auth\CreatesUserProviders.php文件
        $provider = $this->createUserProvider($config['provider'] ?? null);


        $guard = new SessionGuard($name, $provider, $this->app['session.store']);

        # 18. 到这里我们知道
        # $provide 是EloquentUserProvider实例
        # $guard 是 SessionGuard实例 
        # 在回头看第3步,Auth::guard()->attempt()
        # 接下来看下面Illuminate\Auth\SessionGuard.php中的attempt()
        
        // 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;
    }

    /**
     * Create a token based authentication guard.
     *
     * @param  string  $name
     * @param  array  $config
     * @return \Illuminate\Auth\TokenGuard
     */
    public function createTokenDriver($name, $config)
    {
        // The token guard implements a basic API token based guard implementation
        // that takes an API token field from the request and matches it to the
        // user in the database or another persistence layer where users are.
        $guard = new TokenGuard(
            $this->createUserProvider($config['provider'] ?? null),
            $this->app['request']
        );

        $this->app->refresh('request', $guard, 'setRequest');

        return $guard;
    }

    /**
     * Get the guard configuration.
     *
     * @param  string  $name
     * @return array
     */
    protected function getConfig($name)
    {
        # 9. $name = 'web'
        # 读取config/auth.php 中的guards数组中的{$name}值,
        # 对应 'web' => ['driver' => 'session','provider' => 'users',],
        return $this->app['config']["auth.guards.{$name}"];
    }

    /**
     * Get the default authentication driver name.
     *
     * @return string
     */
    public function getDefaultDriver()
    {
        # 6. 读取config/auth.php 中的default数组中的guard值,默认是web
        return $this->app['config']['auth.defaults.guard'];
    }

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

    /**
     * Set the default authentication driver name.
     *
     * @param  string  $name
     * @return void
     */
    public function setDefaultDriver($name)
    {
        $this->app['config']['auth.defaults.guard'] = $name;
    }

    /**
     * Register a new callback based request guard.
     *
     * @param  string  $driver
     * @param  callable  $callback
     * @return $this
     */
    public function viaRequest($driver, callable $callback)
    {
        return $this->extend($driver, function () use ($callback) {
            $guard = new RequestGuard($callback, $this->app['request'], $this->createUserProvider());

            $this->app->refresh('request', $guard, 'setRequest');

            return $guard;
        });
    }

    /**
     * Get the user resolver callback.
     *
     * @return \Closure
     */
    public function userResolver()
    {
        return $this->userResolver;
    }

    /**
     * Set the callback to be used to resolve users.
     *
     * @param  \Closure  $userResolver
     * @return $this
     */
    public function resolveUsersUsing(Closure $userResolver)
    {
        $this->userResolver = $userResolver;

        return $this;
    }

    /**
     * Register a custom driver creator Closure.
     *
     * @param  string  $driver
     * @param  \Closure  $callback
     * @return $this
     */
    public function extend($driver, Closure $callback)
    {
        $this->customCreators[$driver] = $callback;

        return $this;
    }

    /**
     * Register a custom provider creator Closure.
     *
     * @param  string  $name
     * @param  \Closure  $callback
     * @return $this
     */
    public function provider($name, Closure $callback)
    {
        $this->customProviderCreators[$name] = $callback;

        return $this;
    }

    /**
     * Dynamically call the default driver instance.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        return $this->guard()->{$method}(...$parameters);
    }
}

Illuminate\Auth\CreatesUserProviders.php

<?php

namespace Illuminate\Auth;

use InvalidArgumentException;

trait CreatesUserProviders
{
    /**
     * The registered custom provider creators.
     *
     * @var array
     */
    protected $customProviderCreators = [];

    /**
     * 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)
    {
        # $provider = 'users'
        
        # 13. 获取提供器的配置
        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
            );
        }

        # 15. $config = ['driver' => 'eloquent','model' => App\User::class,]
        # $driver ='eloquent'
        switch ($driver) {
            case 'database':
                return $this->createDatabaseProvider($config);
            case 'eloquent':
                # 16. 调用createEloquentProvider方法
                return $this->createEloquentProvider($config);
            default:
                throw new InvalidArgumentException(
                    "Authentication user provider [{$driver}] is not defined."
                );
        }
    }

    /**
     * Get the user provider configuration.
     *
     * @param  string|null  $provider
     * @return array|null
     */
    protected function getProviderConfiguration($provider)
    {
        # $provider = 'users'
        
        # 14. 读取config/auth.php 中的providers数组中的{$provider}值
        # 最终对应 'users' => ['driver' => 'eloquent','model' => App\User::class,],
        if ($provider = $provider ?: $this->getDefaultUserProvider()) {
            return $this->app['config']['auth.providers.'.$provider];
        }
    }

    /**
     * Create an instance of the database user provider.
     *
     * @param  array  $config
     * @return \Illuminate\Auth\DatabaseUserProvider
     */
    protected function createDatabaseProvider($config)
    {
        $connection = $this->app['db']->connection();

        return new DatabaseUserProvider($connection, $this->app['hash'], $config['table']);
    }

    /**
     * Create an instance of the Eloquent user provider.
     *
     * @param  array  $config
     * @return \Illuminate\Auth\EloquentUserProvider
     */
    protected function createEloquentProvider($config)
    {
         # 17. 实例化EloquentUserProvider,
         # 记住提供器最终得到了EloquentUserProvider实例
         # 返回 12
        return new EloquentUserProvider($this->app['hash'], $config['model']);
    }

    /**
     * Get the default user provider name.
     *
     * @return string
     */
    public function getDefaultUserProvider()
    {
        return $this->app['config']['auth.defaults.provider'];
    }
}

Illuminate\Auth\SessionGuard.php 摘取主要涉及方法

    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)) {
            # 19. 调用hasValidCredentials()
            # 22. 如果返回真 定位到login()方法
            $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;
    }
    
    /**
     * Determine if the user matches the credentials.
     *
     * @param  mixed  $user
     * @param  array  $credentials
     * @return bool
     */
    protected function hasValidCredentials($user, $credentials)
    { 
        # 20. 我们已经分析过$this->provider,默认得到的是EloquentUserProvider实例
        # 查看下面文件 Illuminate\Auth\EloquentUserProvider.php
        return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
    }
    
    /**
     * Log a user into the application.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  bool  $remember
     * @return void
     */
    public function login(AuthenticatableContract $user, $remember = false)
    {
         # 23. 登录成功,存储session等操作。。。分析到此结束
        $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);
    }

Illuminate\Auth\EloquentUserProvider.php 摘取主要涉及方法

    /**
     * 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)
    {
        # 21. 最终这里进行了用户的验证,验证通过返回true,不通过返回false
        $plain = $credentials['password'];

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

以上只是简单的分析了login的大体流程,感兴趣大家可以按照这个思路继续分析其他源码,有机会希望多交流,互相进步

以上是关于laravel5.5用户认证源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Shiro源码分析----认证流程

Shiro源码分析----认证流程

spring security认证源码分析之账户权限

django的RBAC认证z;自定义auth_user表;认证组件权限组件源码分析;认证组件;权限组件

laravel5.5 调用系统自带登陆认证auth

Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )(代码片段