laravel修改使用者模組的密碼驗證

wsAdmin發表於2021-09-27

做專案的時候,使用者認證幾乎是必不可少的,如果我們的專案由於一些原因不得不使用 users 之外的使用者表進行認證,那麼就需要多做一點工作來完成這個功能。
現在假設我們只需要修改登入使用者的表,表名和表結構都與框架預設的表users不同,文件沒有教我們如何去做,但是別慌,稍微看下框架實現使用者認證的原始碼就能輕鬆實現。


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

表 admins

id login_name login_pass
1 admin 2y2y10$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表進行登入了。


Larval 5.4的預設Auth登陸傳入郵件和使用者密碼到attempt 方法來認證,通過email 的值獲取,如果使用者被找到,經雜湊運算後儲存在資料中的password將會和傳遞過來的經雜湊運算處理的passwrod值進行比較。如果兩個經雜湊運算的密碼相匹配那麼將會為這個使用者開啟一個認證Session。
參考上面的分析,我們就需要對EloquentUserProvider中的validateCredentials方法進行重寫,步驟如下:

1. 修改 App\Models\User.php 新增如下程式碼

public function getAuthPassword()
{
    return ['password' => $this->attributes['password'], 'salt' => $this->attributes['salt']];
}

2. 建立一個自己的UserProvider.php 的實現

<?php 
namespace App\Foundation\Auth;

use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Str;

/**
* 重寫使用者密碼校驗邏輯
* Class GfzxEloquentUserProvider
* @package App\Foundation\Auth
*/
class GfzxEloquentUserProvider extends EloquentUserProvider
{
    /**
    * 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)
    {
        $plain = $credentials['password'];
        $authPassword = $user->getAuthPassword();
        return md5($plain . $authPassword['salt']) == $authPassword['password'];
    }
}

3. 將User Providers換成我們自己的GfzxEloquentUserProvider

修改 app/Providers/AuthServiceProvider.php

<?php

namespace App\Providers;

use App\Foundation\Auth\GfzxEloquentUserProvider;
use Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
    * Register any authentication / authorization services.
    *
    * @return void
    */
    public function boot()
    {
        $this->registerPolicies();

        Auth::provider('gfzx-eloquent', function ($app, $config) {
            return new GfzxEloquentUserProvider($this->app['hash'], $config['model']);
        });
    }
}

4. 修改 config/auth.php

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

這是就可以用過salt+passwrod的方式密碼認證了

文章來源

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章