如何在一個 Laravel 應用中使用多個認證看守器(Guards)


Laravel

如果你使用 Laravel 有一段時間了,你應該聽過很多關於多種身份認證的信息。你也應該聽過很多認證看守器。但是如果你對 Laravel 還不熟悉,多種身份認證可以讓不同類別的用戶訪問相同應用的不同/類似的部分。

你可能希望在你的 Laravel 應用程序中使用多種身份認證的原因有很多。例如,你有一個運行整個公司的大型應用程序。客戶還通過相同的應用程序與公司的產品和服務進行交互。該應用程序應該還有一個博客,公司中還有一個部門專門管理這個博客。

從上面的程序可以看出,這里面已經有三組用戶。客戶,我們可以讓他們使用一個特定的身份驗證過程來訪問應用;作者,他們應該有另一個身份驗證過程,甚至有權限來啟用一個更健壯的內容管理流程;公司的其他成員,您應該根據他們不同的角色來給他們展示不同的功能菜單。
現在,讓我們看看如何為不同類別的用戶創建多個身份驗證。

要求

  1. 知識儲備 PHP (版本 >= 7.1.3)。
  2. 知識儲備 Laravel (版本 5.6.x)。
  3. 電腦已安裝Composer  (版本 >= 1.3.2)。
  4. 電腦已安裝 Laravel 框架 。

開始

如果你已經滿足上面清單上的所有條件,那么你可以繼續本教程, 我們將創建擁有三個用戶類的應用程序 — admin writeruser。我們將為這三個用戶類建立不同的 guards限制。

創建應用

我們創建一個新的Laravel應用程序,在終端執行下面的命令可以創建一個新的Laravel應用程序。

    $ laravel new multi-auth
    $ cd multi-auth

創建數據庫

我們在應用程序中使用SQLite數據庫。 它是一個輕量級並且非常快的文件類型數據庫。 我們可以在命令行中創建數據庫:

    $ touch database/database.sqlite

在你的應用中,打開 .env 文件並把下面的配置:

    DB_CONNECTION=mysql
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=homestead
    DB_USERNAME=homestead
    DB_PASSWORD=secret

修改為:

    DB_CONNECTION=/absolute/path/to/database.sqlite

這將會確保讓我們的應用使用SQLite數據庫驅動程序。

創建數據庫遷移

我們將會為adminswriters 表創建數據表遷移文件,類似users表的遷移文件。這都是簡單的用戶表,將來你可以根據你的需求來擴展他們。

創建admins數據表遷移文件

運行下面的命令創建admins數據表遷移文件:

    $ php artisan make:migration create_admins_table

在 database/migrations 目錄, 打開並修改admins數據表遷移文件:

    // database/migrations/<timestamp>_create_admins_table.php

    [...]
    public function up()
    {
        Schema::create('admins', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->string('password');
            $table->boolean('is_super')->default(false);
            $table->rememberToken();
            $table->timestamps();
        });
    }
    [...]

我們創建了一個簡單的數據表遷移文件,並定義了我們要使用的數據表的字段。Eloquent為我們提供了方法來定義數據庫表字段的數據類型。我們使用它們定義表字段的數據類型。

記住,你可以按你的想法配置你的表。

Create migration for writers

通過以下命令來創建 writers 的數據遷移表:

    $ php artisan make:migration create_writers_table

打開剛才創建的遷移表文件進行修改:

    database/migrations/<timestamp>_create_writers_table.php
    [...]
    public function up()
    {
        Schema::create('writers', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->string('password');
            $table->boolean('is_editor')->default(false);
            $table->rememberToken();
            $table->timestamps();
        });
    }
    [...]

我們僅僅創建了一個簡單的遷移文件並定義了幾個需要的字段。Eloquent 提供了很多定義數據庫表字段類型的方法,所以操作起來比較容易。

遷移數據庫

現在我們已經定義了表,我們開始遷移數據庫:

    $ php artisan migrate

設置模型

我們的應用程序有不同類別的用戶,他們存儲在不同的數據庫表中,要為使用這些不同的表的用戶進行身份驗證,我們必須為它們定義模型。這些模型類似於用戶模型並擴展可驗證類。

Admin 模型

執行下面的命令,創建 Admin 模型:

    $ php artisan make:model Admin

打開 Admin 模型文件 app/Admin.php,添加如下代碼:

    // app/Admin.php
    <?php

    namespace App;

    use Illuminate\Notifications\Notifiable;
    use Illuminate\Foundation\Auth\User as Authenticatable;

    class Admin extends Authenticatable
    {
        use Notifiable;

        protected $guard = 'admin';

        protected $fillable = [
            'name', 'email', 'password',
        ];

        protected $hidden = [
            'password', 'remember_token',
        ];
    }

當你要對一個模型的訪問做用戶認證,但又不想用默認的 user 看守器時,就需要指定看守器。在這個例子中,這個看守器就是 admin

fillable 數組中,指定了用戶可以操作的數據庫字段。也就是明確地告知 Laravel:

當我執行 createupdate 方法時,我會把要操作的字段以數組的形式傳給你,但是你只可以往數據庫中插入 fillable 數組中指定的字段。

這樣,就可以防止用戶任意操作我們不希望被更改的字段。

hidden 數組中,可以指定不希望被返回的字段。

Writers 模型

開始為 Writer 創建模型,我們運行下面的命令:

    $ php artisan make:model Writer

接着我們打開 Writer 模型,用下面的代碼替換掉:

    // app/Writer.php
    <?php

    namespace App;

    use Illuminate\Notifications\Notifiable;
    use Illuminate\Foundation\Auth\User as Authenticatable;

    class Writer extends Authenticatable
    {
        use Notifiable;

        protected $guard = 'writer';

        protected $fillable = [
            'name', 'email', 'password',
        ];

        protected $hidden = [
            'password', 'remember_token',
        ];
    }

定義守衛(Guards)

Laravel 的守衛定義了如何為每個請求進行身份驗證。默認有一些用於身份驗證的守衛,但也可以自定義。 這樣可以使用 Laravel 默認的認證系統和自定義的 AdminWriter 模型。

打開 config/auth.php 並添加新的守衛,如下:

    // config/auth.php

    <?php

    [...]
    'guards' => [
        [...]
        'admin' => [
            'driver' => 'session',
            'provider' => 'admins',
        ],
        'writer' => [
            'driver' => 'session',
            'provider' => 'writers',
        ],
    ],
    [...]

添加了兩個新的守衛 adminwriter,並設置了提供者。通過提供者來進行身份驗證。

現在,完善 providers 數組:

    // config/auth.php

    [...]
    'providers' => [
        [...]
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Admin::class,
        ],
        'writers' => [
            'driver' => 'eloquent',
            'model' => App\Writer::class,
        ],
    ],
    [...]

根據上一步定義的看守器,我們具體完善了 providers 數組的信息。用 eloquent 作為驅動,是因為我們要用 Eloquent ORM 與數據庫交互。

假設我們要用別的 ORM 比如 RedBeanPHP 來操作數據庫,那就要把驅動設置為 redbeanphp,而不是 eloquent。 至於 model ,就是具體要進行訪問驗證的模型類。

設置控制器

為了使這些看守器發揮各自的作用,我們有兩種方案可以選擇,一是修改現有的驗證控制器,二是創建新的控制器。你可以根據具體需要進行選擇。在這個例子中,我們選擇前者。

修改 LoginController

打開 LoginController,並做如下編輯:

    // app/Http/Controllers/Auth/LoginController.php

    <?php

    namespace App\Http\Controllers\Auth;

    use App\Http\Controllers\Controller;
    use Illuminate\Foundation\Auth\AuthenticatesUsers;
    [...]
    use Illuminate\Http\Request;
    use Auth;
    [...]
    class LoginController extends Controller
    {
        [...]
        public function __construct()
        {
            $this->middleware('guest')->except('logout');
            $this->middleware('guest:admin')->except('logout');
            $this->middleware('guest:writer')->except('logout');
        }
        [...]
    }

通過中間件的方式限制訪問控制器里的方法。我們細分了訪客的類型,這樣,以一個類型的用戶登錄之后,如果再想切換身份,以另一種類型的用戶登錄時,就會被引導到預定義的身份驗證頁面。

舉個例子: 如果我在電腦上以管理員的身份登錄了,我的作家同事就無法以作者的身份登錄他的賬戶。

這個驗證操作是很重要的,這樣就不會因為 session 信息的混亂,而給應用數據帶來潛在的風險。

現在定義 admins 登陸:

    // app/Http/Controllers/Auth/LoginController.php

    [...]
    public function showAdminLoginForm()
    {
        return view('auth.login', ['url' => 'admin']);
    }

    public function adminLogin(Request $request)
    {
        $this->validate($request, [
            'email'   => 'required|email',
            'password' => 'required|min:6'
        ]);

        if (Auth::guard('admin')->attempt(['email' => $request->email, 'password' => $request->password], $request->get('remember'))) {

            return redirect()->intended('/admin');
        }
        return back()->withInput($request->only('email', 'remember'));
    }
    [...]

我們已經設置了一個方法來返回管理員的登錄頁面。 我們將對所有用戶類型使用相同的頁面,並僅更改它們發送到的 URL 。 為我們節省了許多我們可以避免編寫的代碼。

我們還定義了 adminLogin 方法,該方法檢查是否提供了正確的憑據。 然后我們嘗試使用 admin guard 登錄用戶。 在嘗試登錄時設置此守衛非常重要,以便Auth Facade將檢查正確的表匹配憑據。 它還將設置我們的身份驗證,以便我們可以根據登錄用戶的類型限制頁面。
我們將經過身份驗證的用戶重定向到特定 URL,並將未經身份驗證的用戶返回登錄頁面。

現在,讓我們為 writers 也做同樣的事情:

    // app/Http/Controllers/Auth/LoginController.php

    [...]
    public function showWriterLoginForm()
    {
        return view('auth.login', ['url' => 'writer']);
    }

    public function writerLogin(Request $request)
    {
        $this->validate($request, [
            'email'   => 'required|email',
            'password' => 'required|min:6'
        ]);

        if (Auth::guard('writer')->attempt(['email' => $request->email, 'password' => $request->password], $request->get('remember'))) {

            return redirect()->intended('/writer');
        }
        return back()->withInput($request->only('email', 'remember'));
    }
    [...]

我們的登錄設置好了。萬歲!!!

修改 RegisterController

打開 RegisterController 並進行如下編輯:

    // app/Http/Controllers/Auth/RegisterController.php

    <?php
    [...]
    namespace App\Http\Controllers\Auth;
    use App\User;
    use App\Admin;
    use App\Writer;
    use App\Http\Controllers\Controller;
    use Illuminate\Support\Facades\Hash;
    use Illuminate\Support\Facades\Validator;
    use Illuminate\Foundation\Auth\RegistersUsers;
    use Illuminate\Http\Request;
    [...]
    class RegisterController extends Controller
    {
        [...]
        public function __construct()
        {
            $this->middleware('guest');
            $this->middleware('guest:admin');
            $this->middleware('guest:writer');
        }
      [...]
    }

我們已經設置了中間件,控制器將來會使用的到,就像我們使用 LoginController一樣。

現在,讓我們設置為不同用戶返回不同的注冊頁面的方法:

    // app/Http/Controllers/Auth/RegisterController.php

    [...]
    public function showAdminRegisterForm()
    {
        return view('auth.register', ['url' => 'admin']);
    }

    public function showWriterRegisterForm()
    {
        return view('auth.register', ['url' => 'writer']);
    }
    [...]

這跟我們在顯示不同登錄頁面時所做的類似。

現在,我們可以定義一個創建 admin 的方法:

    // app/Http/Controllers/Auth/RegisterController.php

    [...] 
    protected function createAdmin(Request $request)
    {
        $this->validator($request->all())->validate();
        $admin = Admin::create([
            'name' => $request['name'],
            'email' => $request['email'],
            'password' => Hash::make($request['password']),
        ]);
        return redirect()->intended('login/admin');
    }
    [...] 

接下來,讓我們定義創建 writer 的方法:

    // app/Http/Controllers/Auth/RegisterController.php

    [...] 
    protected function createWriter(Request $request)
    {
        $this->validator($request->all())->validate();
        $writer = Writer::create([
            'name' => $request['name'],
            'email' => $request['email'],
            'password' => Hash::make($request['password']),
        ]);
        return redirect()->intended('login/writer');
    }
    [...] 

注冊完成。

設置驗證頁面

我們將用 Laravel 自帶的腳手架為驗證系統生成頁面和控制器。執行如下命令:

    $ php artisan make:auth

這樣就可以生成模版文件 resources/views/auth, 同時還會生成相應的路由,從而完成基本的驗證操作。很酷對不對?

打開 login.blade.php 文件並做如下編輯:

    // resources/views/auth/login.blade.php
    [...]
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header"> {{ isset($url) ? ucwords($url) : ""}} {{ __('Login') }}</div>

                    <div class="card-body">
                        @isset($url)
                        <form method="POST" action='{{ url("login/$url") }}' aria-label="{{ __('Login') }}">
                        @else
                        <form method="POST" action="{{ route('login') }}" aria-label="{{ __('Login') }}">
                        @endisset
                            @csrf
        [...]
    </div>

這里我們會判斷是否有 url 參數。如果有,就把這個參數帶到表單提交的路由里。同時也會修改表單頁的頭部信息,從而實現根據不同的登錄參數展示用戶的類型。

打開 register.blade.php 模板文件並編輯,如下:

    // resources/views/auth/register.blade.php

    [...]
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header"> {{ isset($url) ? ucwords($url) : ""}} {{ __('Register') }}</div>

                    <div class="card-body">
                        @isset($url)
                        <form method="POST" action='{{ url("register/$url") }}' aria-label="{{ __('Register') }}">
                        @else
                        <form method="POST" action="{{ route('register') }}" aria-label="{{ __('Register') }}">
                        @endisset
                            @csrf
        [...]
    </div>

我們復制了登錄頁面的操作。

創建經過身份驗證的用戶將訪問的頁面

現在,我們已經完成了登錄和注冊頁面的設置,讓我們讓 admin 和 writers 在驗證頁面時看到這些頁面。打開終端並運行以下命令來創建新文件。接下來,我們將把相應的代碼片段插入到文件中。

    $ touch resources/views/layouts/auth.blade.php
    $ touch resources/views/admin.blade.php
    $ touch resources/views/writer.blade.php
    $ touch resources/views/home.blade.php

將此代碼塊插入 auth.blade.php 文件:

    // resources/views/layouts/auth.blade.php

    <!DOCTYPE html>
    <html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <!-- CSRF Token -->
        <meta name="csrf-token" content="{{ csrf_token() }}">

        <title>{{ config('app.name', 'Laravel') }}</title>

        <!-- Scripts -->
        <script src="{{ asset('js/app.js') }}" defer></script>

        <!-- Fonts -->
        <link rel="dns-prefetch" href="https://fonts.gstatic.com">
        <link href="https://fonts.googleapis.com/css?family=Raleway:300,400,600" rel="stylesheet" type="text/css">

        <!-- Styles -->
        <link href="{{ asset('css/app.css') }}" rel="stylesheet">
    </head>
    <body>
        <div id="app">
            <nav class="navbar navbar-expand-md navbar-light navbar-laravel">
                <div class="container">
                    <a class="navbar-brand" href="{{ url('/') }}">
                        {{ config('app.name', 'Laravel') }}
                    </a>
                    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
                        <span class="navbar-toggler-icon"></span>
                    </button>

                    <div class="collapse navbar-collapse" id="navbarSupportedContent">
                        <!-- Left Side Of Navbar -->
                        <ul class="navbar-nav mr-auto">

                        </ul>

                        <!-- Right Side Of Navbar -->
                        <ul class="navbar-nav ml-auto">
                            <!-- Authentication Links -->
                           <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                    Hi There <span class="caret"></span>
                                </a>

                                <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="{{ route('logout') }}"
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        {{ __('Logout') }}
                                    </a>

                                    <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                                        @csrf
                                    </form>
                                </div>
                            </li>
                        </ul>
                    </div>
                </div>
            </nav>

            <main class="py-4">
                @yield('content')
            </main>
        </div>
    </body>
    </html>

接下來, 將此代碼塊插入 admin.blade.php 文件:

    // resources/views/admin.blade.php

    @extends('layouts.auth')

    @section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">Dashboard</div>

                    <div class="card-body">
                        Hi boss!
                    </div>
                </div>
            </div>
        </div>
    </div>
    @endsection

打開  writer.blade.php 模板文件並編輯,如下:

    // resources/views/writer.blade.php

    @extends('layouts.auth')

    @section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">Dashboard</div>

                    <div class="card-body">
                        Hi there, awesome writer
                    </div>
                </div>
            </div>
        </div>
    </div>
    @endsection

最后, 打開  home.blade.php  模板文件並替換為:

    // resources/views/home.blade.php

    @extends('layouts.auth')

    @section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">Dashboard</div>

                    <div class="card-body">
                         Hi there, regular user
                    </div>
                </div>
            </div>
        </div>
    </div>
    @endsection

設置路由

我們的應用程序准備差不多了。讓我們定義到目前為止創建的所有方法的路由。打開 routes/web.php 並替換為:

    // routes/web.php

    <?php
    Route::view('/', 'welcome');
    Auth::routes();

    Route::get('/login/admin', 'Auth\LoginController@showAdminLoginForm');
    Route::get('/login/writer', 'Auth\LoginController@showWriterLoginForm');
    Route::get('/register/admin', 'Auth\RegisterController@showAdminRegisterForm');
    Route::get('/register/writer', 'Auth\RegisterController@showWriterRegisterForm');

    Route::post('/login/admin', 'Auth\LoginController@adminLogin');
    Route::post('/login/writer', 'Auth\LoginController@writerLogin');
    Route::post('/register/admin', 'Auth\RegisterController@createAdmin');
    Route::post('/register/writer', 'Auth\RegisterController@createWriter');

    Route::view('/home', 'home')->middleware('auth');
    Route::view('/admin', 'admin');
    Route::view('/writer', 'writer');

修改我們的用戶在經過身份驗證后的重定向

當用戶經過身份驗證時,修改用戶的重定向方式非常重要。默認情況下,Laravel將所有經過身份驗證的用戶重定向到  /home。如果不修改重定向,將會得到下面的錯誤。

所以, 為了解決這個, 打開 app/Http/Controllers/Middleware/RedirectIfAuthenticated.php 文件並替換為:

    // app/Http/Controllers/Middleware/RedirectIfAuthenticated.php

    <?php

    namespace App\Http\Middleware;

    use Closure;
    use Illuminate\Support\Facades\Auth;

    class RedirectIfAuthenticated
    {
        public function handle($request, Closure $next, $guard = null)
        {
            if ($guard == "admin" && Auth::guard($guard)->check()) {
                return redirect('/admin');
            }
            if ($guard == "writer" && Auth::guard($guard)->check()) {
                return redirect('/writer');
            }
            if (Auth::guard($guard)->check()) {
                return redirect('/home');
            }

            return $next($request);
        }
    }

RedirectIfAuthenticated 中間件接收 auth 守衛作為參數。當我們試圖訪問任何針對經過身份驗證的用戶的頁面時,將觸發此中間件。然后,我們可以確定用戶擁有的身份驗證類型並相應地重定向它們。

修改身份驗證異常處理程序

當用戶被重定向時會發生一件煩人的事情。如果用戶試圖訪問 /writer,但沒有經過身份驗證,那么用戶會被重定向到 /login/writer,他們被重定向到 /login 這不是我們想要的。

為了確保當用戶嘗試訪問 /writer 時,它們被重定向到 /login/writer 或者同樣用於 /admin,我們必須修改異常處理程序。 在 app/Exceptions 中打開處理程序文件並添加以下內容:

    // app/Exceptions/Handler.php

    <?php

    namespace App\Exceptions;

    use Exception;
    use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
    [...]
    use Illuminate\Auth\AuthenticationException;
    use Auth; 
    [...]
    class Handler extends ExceptionHandler
    {
       [...] 
        protected function unauthenticated($request, AuthenticationException $exception)
        {
            if ($request->expectsJson()) {
                return response()->json(['error' => 'Unauthenticated.'], 401);
            }
            if ($request->is('admin') || $request->is('admin/*')) {
                return redirect()->guest('/login/admin');
            }
            if ($request->is('writer') || $request->is('writer/*')) {
                return redirect()->guest('/login/writer');
            }
            return redirect()->guest(route('login'));
        }
    }

我們剛剛添加的 unauthenticated 方法解決了我們遇到的這個問題。 它默認接收一個 AuthenticationExpection 異常,它攜帶該保護信息。 遺憾的是,我們無法訪問它,因為它受到保護(希望Laravel 5.7能夠提供訪問它的方法)。

我們的解決方法是使用 request→is()。這會檢查我們嘗試訪問的URL。如果我們沒有絕對URL或者我們有路由組,它也可以檢查URL模式。

在我們的例子中,我們首先檢查是否收到了 JSON 請求並單獨處理異常。 然后我們檢查我們是否正在嘗試訪問 /admin 或任何以 admin 開頭的 URL。 我們將用戶重定向到相應的登錄頁面。 我們也檢查 writer

這對我們來說是一個很好的解決方法,但這意味着我們必須知道我們想要訪問的絕對URL,或者至少對我們的守衛保護的所有路由都有相同的前綴。

運行應用程序

現在我們的應用程序已經准備好了,運行下面的命令來啟動它:

    $ php artisan serve

它通常應該是可用的 http://localhost:8000.

先通過訪問 http://localhost:8000/register/writer 和 http://localhost:8000/register/admin 這兩個鏈接來分別注冊作者和管理員. 然后訪問 http://localhost:8000/login/writer 和 http://localhost:8000/login/admin 這兩個鏈接分別登錄作者和管理員。

結語

在本教程中,我們深入研究了 Laravel 身份驗證。 我們定義了多個守衛來處理多個身份驗證和訪問控制。我們還為經過身份驗證的用戶處理重定向,為未經身份驗證的用戶處理重定向。

如果您完全遵循本指南,您將能夠為具有不同用戶類型的應用程序(可能是[多租戶](https://en.wikipedia.org/wiki/Multitenancy)應用程序)設置基本身份驗證。 盡管如此,嘗試擴展你所看到的並分享你想出的東西。

本文中應用程序的源代碼可在 GitHub
轉自:https://learnku.com/laravel/t/26947


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM