
如果你使用 Laravel 有一段時間了,你應該聽過很多關於多種身份認證的信息。你也應該聽過很多認證看守器。但是如果你對 Laravel 還不熟悉,多種身份認證可以讓不同類別的用戶訪問相同應用的不同/類似的部分。
你可能希望在你的 Laravel 應用程序中使用多種身份認證的原因有很多。例如,你有一個運行整個公司的大型應用程序。客戶還通過相同的應用程序與公司的產品和服務進行交互。該應用程序應該還有一個博客,公司中還有一個部門專門管理這個博客。
從上面的程序可以看出,這里面已經有三組用戶。客戶,我們可以讓他們使用一個特定的身份驗證過程來訪問應用;作者,他們應該有另一個身份驗證過程,甚至有權限來啟用一個更健壯的內容管理流程;公司的其他成員,您應該根據他們不同的角色來給他們展示不同的功能菜單。
現在,讓我們看看如何為不同類別的用戶創建多個身份驗證。
要求
- 知識儲備 PHP (版本 >= 7.1.3)。
- 知識儲備 Laravel (版本 5.6.x)。
- 電腦已安裝Composer (版本 >= 1.3.2)。
- 電腦已安裝 Laravel 框架 。
開始
如果你已經滿足上面清單上的所有條件,那么你可以繼續本教程, 我們將創建擁有三個用戶類的應用程序 — admin, writer,user。我們將為這三個用戶類建立不同的 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數據庫驅動程序。
創建數據庫遷移
我們將會為admins 和 writers 表創建數據表遷移文件,類似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:
當我執行
create和update方法時,我會把要操作的字段以數組的形式傳給你,但是你只可以往數據庫中插入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 默認的認證系統和自定義的 Admin 和 Writer 模型。
打開 config/auth.php 並添加新的守衛,如下:
// config/auth.php
<?php
[...]
'guards' => [
[...]
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
'writer' => [
'driver' => 'session',
'provider' => 'writers',
],
],
[...]
添加了兩個新的守衛 admin 和 writer,並設置了提供者。通過提供者來進行身份驗證。
現在,完善 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
