如何在 Laravel 上進行多權限登入管理

因為之前寫 Codeigniter 時習慣把一般用戶與能進後台的用戶在 login 時就把他們分開,所以想說用 laravel

也來實現相同的功能。因為對 laravel 還不熟,所以想遵從 laravel 的規範用最少的修正來完成。

Provider

需求:

  • 該功能基於原有的 users 表不作變動的情況下,讓一位使用者有多個權限,並限制某權限能否登入該系統

    事前準備:

  • 安裝好 Laravel 並且最少建立起能夠執行登入登出的功能

  • 建立一張名為 role 的表,表內最少包含以下欄位

  • id(int)

  • user_id(int)

  • role(int)

開始:

  • 從原有的 Users.php 拷貝一個 UsersModel.php (沿用原有的 Users 也可以)

  • 建立一個 RoleModel.php

  • 在 UsersModel.php 內加上以下方法

    1
    2
    3
    4
    public function roles()
    {
    return $this->hasMany(RoleModel::class, 'user_id');
    }
  • 執行 php artisan make:provider CustomUserProvider 建立一個自己認證用的 Provider

  • 這個 Provider 必需繼承 EloquentUserProvider 或 UserProvider。並且換掉 retrieveByCredentials() 函數的執行內容來進行驗證。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    <?php

    namespace App\Providers;

    use App\Models\UsersModel;
    use Illuminate\Auth\EloquentUserProvider;
    use Illuminate\Support\Str;
    use Illuminate\Contracts\Support\Arrayable;

    /**
    * 自定義登入時驗證項目
    *
    * @author LIN CHENGHUNG <k80092@hotmail.com>
    */
    class CustomUserProvider extends EloquentUserProvider
    {
    /**
    * 透過驗證資訊來比對身份
    *
    * @param array $credentials 驗證資訊
    *
    * @return Illuminate\Contracts\Auth\Authenticatable
    */
    public function retrieveByCredentials(array $credentials)
    {
    if (
    empty($credentials) ||
    (count($credentials) === 1 &&
    Str::contains($this->firstCredentialKey($credentials), 'password'))
    ) {
    return;
    }

    $query = $this->newModelQuery();

    // 追加條件
    $query = $query->whereHas('roles', function ($query) {
    $query->whereRole(config('const.ROLE')['ADMIN']);
    });

    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();
    }
    }
  • 修改 AuthServiceProvider.php 來將寫好的 Provider 註冊

    1
    2
    3
    Auth::provider('myAuthProvider', function ($app, array $config) {
    return new CustomUserProvider($app['hash'], 'App\Models\UsersModel');
    });
  • 修改 Auth.php 中預設的 eloquent provider 換成自己寫的 myAuthProvider

    1
    2
    3
    4
    5
    6
    7
    ......
    'providers' => [
    'users' => [
    'driver' => 'myAuthProvider', // 修改這裡
    'model' => App\Models\UsersModel::class,
    ],
    ......

    Guard

    基本到這裡為止就算沒有實作這個 Guard 也已經完成一個最簡單的限制特定權限登入的功能。不過如果需要針對認證流程再作定義的話,可以在實作一個 Guard 來完成!

  • 建立一個管理員用的 Guard 叫做 AdminGuard(我放在 App\Guards 底下)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <?php

    namespace App\Guards;

    use Illuminate\Auth\SessionGuard;

    /**
    * 管理者守門員
    *
    * @author LIN CHENGHUNG <k80092@hotmail.com>
    */
    class AdminGuard extends SessionGuard
    {
    /**
    * 登入驗證
    *
    * @param array $credentials
    * @param bool $remember
    * @return bool
    */
    public function attempt(array $credentials = [], $remember = false)
    {
    // 自行定義驗證內容
    return parent::attempt($credentials, $remember);
    }
    }
  • 修改 AuthServiceProvider.php 來將寫好的 Guard 註冊

    1
    2
    3
    Auth::extend('admin', function ($app, $name, $config) {
    return new AdminGuard($name, new CustomUserProvider($app['hash'], 'App\Models\UsersModel'), $app['session.store']);
    });
  • 增加 Auth.php 中的 guard

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ......
    'guards' => [
    'web' => [
    'driver' => 'session',
    'provider' => 'users',
    ],
    // 增加這邊
    'admin' => [
    'driver' => 'admin',
    'provider' => 'users',
    ],
    ......
  • 這樣就可以用以下的方式來進行登入

    1
    2
    3
    if (auth('admin')->attempt($credentials, $remember)) {
    // dosomething
    }
  • 然後在像下面這樣設定路由的 guard 來限制哪些是管理者才能訪問的URL

    1
    2
    3
    Route::middleware('auth:admin')->get('test',function() {
    echo 'test';
    });

    代碼:

Github

參考:

分析 Auth(3)--客製化驗證機制

Laravel 认证原理及完全自定义认证

Laravel 台灣