Laravel 中自定義使用者登入的資料表

Jeffrey發表於2019-04-22

做專案的時候,使用者認證幾乎是必不可少的,如果我們的專案由於一些原因不得不使用 users 之外的使用者表進行認證,那麼就需要多做一點工作來完成這個功能。

現在假設我們只需要修改登入使用者的表,表名和表結構都與框架預設的表users不同,文件沒有教我們如何去做,但是別慌,稍微看下框架實現使用者認證的原始碼就能輕鬆實現。

首先,自定義一張表用來登入,表結構和模擬資料如下:

表 admins

id login_name login_pass
1 admin $2y$10$2MUhp7b6ghVOngb/.b/x6uuEW/yL3FqPKJztawrM0U577Clf07xda

從配置檔案入手

使用者認證相關的配置都儲存在config/auth.php檔案中,先來看看配置檔案的內容:

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option controls the default authentication "guard" and password
    | reset options for your application. You may change these defaults
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | here which uses session storage and the Eloquent user provider.
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | Supported: "session", "token"
    |
    */

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

        'api' => [
            'driver' => 'passport',
            'provider' => 'users',
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | If you have multiple user tables or models you may configure multiple
    | sources which represent each model / table. These sources may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

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

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | You may specify multiple password reset configurations if you have more
    | than one user table or model in the application and you want to have
    | separate password reset settings based on the specific user types.
    |
    | The expire time is the number of minutes that the reset token should be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    */

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
        ],
    ],

];

預設使用的守衛是web,而web守衛使用的認證驅動是session,使用者提供器是users。假設我們的需求只是將使用者的提供器由users改為admins,那麼我們需要做兩步操作:

  • 修改預設的使用者提供器,將provider=>'users'改為provider=>'admins'
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
    ],
  • 配置admins提供器,假設依舊使用eloquent作為驅動,並建立好了admins表的模型
    'providers' => [
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Admin::class
        ]
    ],

使用Auth門面的attempt方法進行登入

SessionGuard 中的attempt方法:

//Illuminate\Auth\SessionGuard
 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)) {
            $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;
    }

該方法中呼叫 UserProvider 介面的retrieveByCredentials方法檢索使用者,根據我們的配置,UserProvider介面的具體實現應該是EloquentUserProvider,因此,我們定位到EloquentUserProviderretrieveByCredentials方法:

//Illuminate\Auth\EloquentUserProvider
public function retrieveByCredentials(array $credentials)
    {
        if (empty($credentials) ||
           (count($credentials) === 1 &&
            array_key_exists('password', $credentials))) {
            return;
        }

        // First we will add each credential element to the query as a where clause.
        // Then we can execute the query and, if we found a user, return it in a
        // Eloquent User "model" that will be utilized by the Guard instances.
        $query = $this->createModel()->newQuery();

        foreach ($credentials as $key => $value) {
            if (Str::contains($key, 'password')) {
                continue;
            }

            if (is_array($value) || $value instanceof Arrayable) {
                $query->whereIn($key, $value);
            } else {
                $query->where($key, $value);
            }
        }

        return $query->first();
    }

該方法會使用傳入的引數(不包含password)到我們配置的資料表中搜尋資料,查詢到符合條件的資料之後返回對應的使用者資訊,然後attempt方法會進行密碼校驗,校驗密碼的方法為:

//Illuminate\Auth\SessionGuard
/**
     * Determine if the user matches the credentials.
     *
     * @param  mixed  $user
     * @param  array  $credentials
     * @return bool
     */
    protected function hasValidCredentials($user, $credentials)
    {
        return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
    }

進一步檢視EloquentUserProvider中的validateCredentials方法

//Illuminate\Auth\EloquentUserProvider
public function validateCredentials(UserContract $user, array $credentials)
{
    $plain = $credentials['password'];

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

通過validateCredentials可以看出,提交的認證資料中密碼欄位名必須是password,這個無法自定義。同時可以看到,入參$user必須實現Illuminate\Contracts\Auth\Authenticatable介面(UserContract是別名)。

修改 Admin 模型

Admin模型必須實現Illuminate\Contracts\Auth\Authenticatable介面,可以借鑑一下User模型,讓Admin直接繼承Illuminate\Foundation\Auth\User 就可以,然後重寫getAuthPassword方法,正確獲取密碼欄位:

// App\Admin
public function getAuthPassword()
{
    return $this->login_pass;
}

不出意外的話,這個時候就能使用admins表進行登入了。

總結

laravel提供了很強的擴充套件性,幾乎一切都可以個性化定製,但是需要我們耐心看看原始碼和文件,程式碼註釋很詳細,這裡只是簡單的介紹了一下自定義使用者登入的表,實際業務需求往往很複雜,需要定製的功能會更多,但是隻要按照框架的規範開發,一切都很簡單。

相關文章