1. 程式人生 > >Laravel 使用者認證體系詳解

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\[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]'); $this->post('register', 'Auth\[email protected]'); // Password Reset Routes... $this->get('password/reset', 'Auth\[email protected]
'
); $this->post('password/email', 'Auth\[email protected]'); $this->get('password/reset/{token}', 'Auth\[email protected]'); $this->post('password/reset', 'Auth\[email protected]'); }

本文我們主要來說說登入流程:

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