Laravel 用户认证体系详解

本篇文章基于laravel 5.3。
用户注册、登录及密码找回等功能几乎是web系统的标配。正因如此,laravel将其作为一个独立的部分抽象出来,供开发者使用,极大提高了开发人员的效率。
简单来说,用户认证就是系统对用户提供的登录信息进行校验的过程。这一过程可以抽象为如下几个部分:

  • 用户如何提供登录信息,如何表示?
  • 系统如何校验登录信息?
  • 系统如何维护登录成功后的认证信息?

laravel提供了一套完整的用户认证体系,开箱即用。只需要运行如下命令即可:

php artisan make:auth

通过调用Auth::routes()方法,laravel为用户认证体系注册了以下路由:

/**
 * Register the typical authentication routes for an application.
 *
 * @return void
 */
public function auth()
{
    // Authentication Routes...
    $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
    $this->post('login', 'Auth\LoginController@login');
    $this->post('logout', 'Auth\LoginController@logout')->name('logout');

    // Registration Routes...
    $this->get('register', 'Auth\RegisterController@showRegistrationForm');
    $this->post('register', 'Auth\RegisterController@register');

    // Password Reset Routes...
    $this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm');
    $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail');
    $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm');
    $this->post('password/reset', 'Auth\ResetPasswordController@reset');
}

本文我们主要来说说登录流程:

    /**
     * Handle a login request to the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function login(Request $request)
    {
        // 参数合法性校验,校验规则:[$this->username() => 'required', 'password' => 'required',]
        // 此处用户名字段名是通过username()方法获取的,默认是email,方便开发者自定义字段名称
        $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.
        // 登录尝试次数限制,具体由 Illuminate\Cache\RateLimiter 类实现相关功能
        if ($this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

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

        // 参数过滤,只保留用户名和密码字段
        $credentials = $this->credentials($request);

        /*验证登录信息有效性。此处laravel将具体的认证工作分离,以不同的认证驱动(由Illuminate\Auth\AuthManager类的guard方法获取,默认为web,在config/auth.php中配置)来实现相关逻辑。
        在具体的认证驱动中,attempt方法首先根据认证信息获取相应的用户,然后根据认证信息和获取的用户来校验用户的合法性,最终实现登录用户的合法性校验。
        此处看似简单的登录认证,laravel为了可扩展性,其实现有点绕,涉及的类比较多,就连密码hash部分也独立为单独的类(Illuminate\Hashing\BcryptHasher,实现了Illuminate\Contracts\Hashing\Hasher接口),基本上整个认证过程可变的部分都做了基于接口的编程实现。在config/auth.php配置文件中注释部分有比较详细的说明,可以结合仔细阅读下。
        */
        if ($this->guard()->attempt($credentials, $request->has('remember'))) {
            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 authenticate a user using the given credentials.
     *
     * @param  array  $credentials
     * @param  bool   $remember
     * @param  bool   $login
     * @return bool
     */
    public function attempt(array $credentials = [], $remember = false, $login = true)
    {
        $this->fireAttemptEvent($credentials, $remember, $login);

        $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)) {
            if ($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.
        if ($login) {
            $this->fireFailedEvent($user, $credentials);
        }

        return false;
    }

这里我们多聊下认证驱动。不管何种认证驱动,按照laravel的说法都需要一个User Provider,这个User Provider有什么用呢?简单说就是实现用户信息的保存及获取。laravel定义了一个Illuminate\Contracts\Auth\UserProvider接口,所有User Provider都需要实现这个接口。另外还有一点是laravel对用户进行了抽象,定义了一个Illuminate\Contracts\Auth\Authenticatable接口,上面说的用户都是实现了该接口的类的实例对象。

interface UserProvider
{
    /**
     * Retrieve a user by their unique identifier.
     *
     * @param  mixed  $identifier
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveById($identifier);

    /**
     * Retrieve a user by their unique identifier and "remember me" token.
     *
     * @param  mixed   $identifier
     * @param  string  $token
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByToken($identifier, $token);

    /**
     * Update the "remember me" token for the given user in storage.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  string  $token
     * @return void
     */
    public function updateRememberToken(Authenticatable $user, $token);

    /**
     * Retrieve a user by the given credentials.
     *
     * @param  array  $credentials
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByCredentials(array $credentials);

    /**
     * Validate a user against the given credentials.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  array  $credentials
     * @return bool
     */
    public function validateCredentials(Authenticatable $user, array $credentials);
}

laravel提供了两个User ProviderEloquentUserProviderDatabaseUserProvider。两者的区别在于前者是基于laravel的Eloquent与数据库打交道,后者是更底层些,直接与数据库打交道。看看二者的构造函数便可知一二:

    /**
     * Create a new Eloquent user provider.
     *
     * @param  \Illuminate\Contracts\Hashing\Hasher  $hasher
     * @param  string  $model The Eloquent user model
     * @return void
     */
    public function __construct(HasherContract $hasher, $model)
    {
        $this->model = $model;
        $this->hasher = $hasher;
    }
    /**
     * Create a new database user provider.
     *
     * @param  \Illuminate\Database\ConnectionInterface  $conn
     * @param  \Illuminate\Contracts\Hashing\Hasher  $hasher
     * @param  string  $table
     * @return void
     */
    public function __construct(ConnectionInterface $conn, HasherContract $hasher, $table)
    {
        $this->conn = $conn;
        $this->table = $table;
        $this->hasher = $hasher;
    }

猜你喜欢

转载自blog.csdn.net/wepe12/article/details/78665091