文章轉發自專業的Laravel開發者社區,原始連接:learnku.com/laravel/t/2…php
若是你使用 Laravel 有一段時間了,你應該聽過不少關於多種身份認證的信息。你也應該聽過不少認證看守器。可是若是你對 Laravel 還不熟悉,多種身份認證可讓不一樣類別的用戶訪問相同應用的不一樣/相似的部分。css
你可能但願在你的 Laravel 應用程序中使用多種身份認證的緣由有不少。例如,你有一個運行整個公司的大型應用程序。客戶還經過相同的應用程序與公司的產品和服務進行交互。該應用程序應該還有一個博客,公司中還有一個部門專門管理這個博客。html
從上面的程序能夠看出,這裏面已經有三組用戶。客戶,咱們可讓他們使用一個特定的身份驗證過程來訪問應用;做者,他們應該有另外一個身份驗證過程,甚至有權限來啓用一個更健壯的內容管理流程;公司的其餘成員,您應該根據他們不一樣的角色來給他們展現不一樣的功能菜單。 如今,讓咱們看看如何爲不一樣類別的用戶建立多個身份驗證。mysql
若是你已經知足上面清單上的全部條件,那麼你能夠繼續本教程, 咱們將建立擁有三個用戶類的應用程序 — admin
,writer
,user
。咱們將爲這三個用戶類創建不一樣的 guards
限制。laravel
咱們建立一個新的Laravel
應用程序,在終端執行下面的命令能夠建立一個新的Laravel
應用程序。git
$ laravel new multi-auth
$ cd multi-auth
複製代碼
咱們在應用程序中使用SQLite
數據庫。 它是一個輕量級而且很是快的文件類型數據庫。 咱們能夠在命令行中建立數據庫:github
$ touch database/database.sqlite
複製代碼
在你的應用中,打開 .env
文件並把下面的配置:web
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
複製代碼
修改成:sql
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
爲咱們提供了方法來定義數據庫表字段的數據類型。咱們使用它們定義表字段的數據類型。
記住,你能夠按你的想法配置你的表。
經過如下命令來建立 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 模型:
$ 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
數組中,能夠指定不但願被返回的字段。
開始爲 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',
];
}
複製代碼
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
,並作以下編輯:
// 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
並進行以下編輯:
// 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 身份驗證。 咱們定義了多個守衛來處理多個身份驗證和訪問控制。咱們還爲通過身份驗證的用戶處理重定向,爲未經身份驗證的用戶處理重定向。
若是您徹底遵循本指南,您將可以爲具備不一樣用戶類型的應用程序(多是[多租戶](en.wikipedia.org/wiki/Multit… 儘管如此,嘗試擴展你所看到的並分享你想出的東西。
本文中應用程序的源代碼可在 GitHub。