翻譯、衍生自:https://learnku.com/docs/laravel/5.5/authenticatio...
想要快速開始?在全新的 Laravel 專案中執行 Artisan 命令 php artisan make:auth
和 php artisan migrate
即可急速建立一個具有完整功能的認證系統腳手架程式碼。之後,在瀏覽器位址列輸入 http://your-app.dev/register
就能進入註冊頁面,完整的路由列表請使用 php artisan route:list
命令在控制檯檢視。
簡介
使用 Laravel 構建一個認證系統前所未有的簡單,因為這個功能是開箱就準備好了的。認證相關功能的配置檔案是 config/app.php
。
作為核心功能,Laravel 的認證由「guards」和「providers」組成。Guards 定義請求使用者的認證方式。例如,使用了 session
驅動的 web
Guard 就是用會話和 Cookie 的方式認證的。
Provider 定義從持久化儲存裝置裡獲得使用者資訊的方式,Laravel 開箱提供 Eloquent 和資料庫 查詢語句構造器 兩種驅動方式的 Provider。你也可以自定義其他的 Provider。
如果你現在有些懵逼,沒有關係!這些配置對於許多專案,都不需要修改,保持預設配置就行了。
資料庫注意事項
Laravel 預設使用的 Provider 使用 Eloquent 驅動,對應的 Eloquent 模型是 App\User
。如果你專案裡不使用 Eloquent 驅動,你還可以修改為使用 database
驅動,它是基於查詢語句構造器查詢的。
App\User
模型對應的資料庫表中,要確保密碼欄位至少是 60 個字元長度,當然,保持預設的 255 字元長度是個不錯的選擇。
同時,還要保證你的 users
表中包含一個可為空的、字串型別(100 個字元長度)的 remember_token
欄位,這個欄位用來儲存使用者勾選「記住我」選項時的令牌值。
快速上手
在 Laravel 中已經預置了認證控制器,位於 App\Http\Controllers\Auth
目錄下。RegisterController
處理使用者註冊,LoginController
處理使用者登入,ForgortPasswordController
傳送密碼重置連結,ResetPasswordController
處理重置密碼邏輯。每個控制器都使用了 trait 引入必要的實現方法。對許多專案來說,你可以一點都不修改它們。
路由
執行下面這一條 Artisan 命令,就可以快速建立與認證功能相關的路由和檢視檔案。
php artisan make:auth
在一個全新的 Laravel 專案中,這條命令會生成佈局、登入和註冊檢視,還有認證系統使用的所有路由,一個 HomeController
(讓使用者登入之後跳轉到皮膚頁面)。
檢視
使用 make:auth
建立的檢視檔案包括 resources/views/auth
目錄下的。同時,還有一個佈局檔案 resources/views/layouts/app.balade.php
上面這些檢視檔案使用了 Boostrap 這個 CSS 框架,你也可以自定義使用的框架。
認證
現在路由、檢視和控制器,資料庫表都有了,開始用起來吧!註冊新使用者,然後登入進去。
自定義跳轉路徑
當使用者成功登入後,會跳轉到 /home
地址,這個地址是在 LoginController
、RegisterController
和 ResetPasswordController
中的 redirectTo
屬性中設定的,你可以修改它,以滿足你的需求。
protected $redirectTo = '/';
如果在跳轉時,還有一些通用的跳轉邏輯要處理的話,那麼就定義成一個 redirectTo
方法:
protected function redirectTo()
{
return '/path';
}
redirectTo()
的優先順序高於 redirectTo
屬性。
自定義認證欄位
Laravel 預設使用 email
作為認證關鍵欄位,如果要自定義,在 LoginController
中定義一個 username
方法即可。例如,下面我們將 name
(使用者名稱)欄位作為認證關鍵欄位:
public function username()
{
return 'name';
}
自定義 Guard
你也可以在認證和註冊使用者時,使用自定義「Guard」。然後在 LoginController
、RegisterController
和 ResetPasswordController
中的 guard
方法裡使用它(還方法返回一個 Guard 例項物件)。
use Illuminate\Support\Facades\Auth;
protected function guard()
{
return Auth::guard('guard-name');
}
自定義驗證/儲存
如果你需要修改註冊頁面的表單欄位,或者要自定義儲存使用者到資料庫裡的邏輯,就需要修改 RegisterController
了。
RegisterController
的 validator
方法驗證從前臺註冊頁面傳遞過來的表單欄位,這裡定義了對應欄位的取值規則。你也可以按照需要修改這裡定義的規則。
RegisterController
的 create
方法用來在資料庫中建立一個新的 App\User
記錄(也就是使用者資料),使用 Eloquent ORM。你也可以根據需要修改。
訪問認證使用者
使用 Auth
門面訪問認證使用者。
use Illuminate\Support\Facades\Auth;
// 獲得當前認證使用者
$user = Auth::user();
// 獲得當前認證使用者的 ID
$user = Auth::id();
你也可以使用 Illuminate\Http\Request
例項訪問認證使用者。記住,在控制器方法裡,Request
會被自動注入。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ProfileController extends Controller
{
/**
* Update the user's profile.
*
* @param Request $request
* @return Response
*/
public function update(Request $request)
{
// $request->user() returns an instance of the authenticated user...
}
}
判斷是否是認證使用者
如果使用者登入了當前系統,那麼 Auth
門面的 check
方法會返回 true
,否則返回 false
。
use Illuminate\Support\Facades\Auth;
if (Auth::check) {
// 使用者已登入
}
雖然這種方式能判斷使用者登入與否,但是一般不這麼做,而是在路由定義或者控制器建構函式中使用中介軟體,判斷使用者登入與否。我們繼續往下看。
保護路由
我們可以在定義路由的時候,順便定義使用的中介軟體。Laravel 有一個 auth
中介軟體,在 Illuminate\Auth\Middleware\Authenticate
這個地方,它是在 App\Http\Kernel
中註冊的,就可以用來判斷使用者的登入/認證了嗎。
Route::get('profile', function () {
// 只有認證使用者才能進來
})->middleware('auth');
當然,你也可以在控制器建構函式裡附加中介軟體。
public function __construct()
{
$this->middleware('auth');
}
指定 Guard
為路由附加中介軟體的時候,可以為認證使用者指定使用的 Guard,這裡指定的 Guard 對應 auth.php
中 guards
陣列選項中的 Key 之一。
public function __construct()
{
$this->middleware('auth:api');
}
登入節流
如果你使用的是 Laravel 內建的 LoginController
類,它天然包含了 Illuminate\Foundation\Auth\ThrottlesLogins
trait。預設,如果使用者經過幾次失敗的登入嘗試後,只能再等 1 分鐘後才能登入。該節流限制是以使用者的使用者名稱/郵箱號地址和 IP 地址作為唯一標識的。
手動認證使用者
如果你不用 Laravel 提供的認證控制器。那麼你需要直接使用 Laravel 認證類寫認證邏輯了。不過別擔心,不難。
我們通過 Auth
門面訪問 Laravel 的認證服務,使用 attempt
方法。
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
/**
* Handle an authentication attempt.
*
* @return Response
*/
public function authenticate()
{
if (Auth::attempt(['email' => $email, 'password' => $hashedPassword])) {
// Authentication passed...
return redirect()->intended('dashboard');
}
}
}
attempt
方法接收的是驗證欄位鍵值對,提供的資料會用來在資料庫表中查詢。在上面的例子裡,我們在資料庫裡查詢 email
欄位,如果找到使用者,就會將資料庫中儲存的雜湊密碼與提供的雜湊密碼比較,如果一樣的話,使用者登入成功。
使用者登入成功,attempt
方法就會返回 true
,否則返回 false
。
redirect()
函式返回一個 Redirector
例項物件,該物件的 intended
方法會在使用者認證成功後,重定向到使用者之前(未進入登入頁面前)要去的那個 URL 地址。也可以為 intended
方法新增一個引數—— fallback URI —— 如果 intended
方法去到的那個 URL 無效,就使用這個 URL 地址。
指定額外條件
你也可以在呼叫 attempt
方法的時候新增額外要驗證的欄位。例如,登入時我們順便檢查使用者的「啟用」狀態,只查詢啟用使用者。
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
// The user is active, not suspended, and exists.
}
這裡登入使用的關鍵欄位是 email
,你可以在登入控制器中定義 username
方法,自行修改使用的關鍵欄位。
訪問特定的 Guard 例項
你可以在使用 Auth
門面的 guard
方法時,指定使用的 Guard。就是說,你專案裡的認證方式可以有好幾種,因為每個 Guard 都有可能對應一中的認證方式,其背後對應的可能是一張資料庫表或者是一個 Eloquent Model,它們完全可以單獨組織成一個飽滿的認證方式。
比如,我們在 auth.php
中定義了一個 admin
Guard,與 web
Guard 不同的是,admin
Guard 是從 admin
資料庫表裡獲取認證使用者資料的,而 web
Guard 是從 users
資料庫表裡獲取認證使用者資料的。
if (Auth::guard('admin')->attempt($credentials)) {
//
}
登出
登出賬號,使用 Auth
門面的 logout
方法。該方法會從從會話裡清除使用者認證資訊。
Auth::logout();
記住使用者
如果你需要提供「記住我」的功能,就要為 attempt
方法傳遞第二個布林值引數,它會永遠記住使用者,直到使用者手動登出。當然,users
表中必須包含一個 remember_token
列,用來儲存「記住我」產生的令牌。
if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
// The user is being remembered...
}
如果使用內建的 LoginController
處理登入,那麼已經具備了「記住我」功能,這是在引入的 trait 中定義的。
Auth
門面還提供一個 viaRemember
方法,用來判斷認證使用者是否是勾選了「記住我」功能的使用者,如果是的話,就會返回 true
,否則返回 false
。
if (Auth::viaRemember()) {
//
}
其它認證方法
認證一個使用者例項
如果你已經有一個使用者例項了,那麼把它帶入 Auth::login
方法的登入系統。這是因為 App\User
模型實現了 Illuminate\Contracts\Auth\Authenticatable
這個介面,所以才可以這樣用:
Auth::login($user);
// 登入然後「記住」使用者
Auth::login($user, true);
當然,你也可以選擇使用的 Guard 例項。
Auth::guard('admin')->login($user);
通過 ID 認證使用者
如果已知使用者主鍵 ID,也可以登入系統。使用 Auth::loginUsingId
方法,它接收的是使用者主鍵。
Auth::loginUsingId(1);
// 登入然後「記住」使用者呀
Auth::loginUsingId(1, true);
針對單次請求的使用者認證
還可以使用 Auth::once
方法針對單次請求來認證使用者,這種方式的認證不使用會話和 Cookie,這在構建無狀態的 API 時非常有幫助:
if (Auth::once($credentials)) {
//
}
HTTP 基本認證
HTTP 基本認證 提供了一種快速認證使用者的方式(不需要額外的「登入」頁面)。為了使用它,在路由上附加一個 auth.basic
中介軟體即可,這個中介軟體在 Laravel 中已經註冊好了,你直接用就行。
Route::get('profile', function () {
// 只有認證使用者才能進來
})->middleware('auth.basic');
定義好後,當你在瀏覽器中訪問這個路由的時候,會出現一個彈框,讓你填入使用者名稱和密碼。預設,auth.basic
中介軟體使用 email
作為登入的關鍵欄位,你可以在 LoginController
中新增 username
方法覆蓋此約定。
關於 FastCGI 的注意事項
如果你是使用 PHP FastCGI,HTTP 基礎認證可能會不生效。這時,將下面的兩行配置內容新增到 .htaccess
檔案即可:
RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
無狀態 HTTP 基本認證
你也可以在不借助會話和 Cookie 的情況下使用 HTTP 基本認證,用來作為 API 認證使用,這稱為「無狀態 HTTP 基本認證」。我們需要新定義一箇中介軟體,在中介軟體裡呼叫 Auth
門面的 onceBasic
方法,如果 onceBasic
沒有返回響應,則允許請求的進一步處理。
<?php
namespace Illuminate\Auth\Middleware;
use Illuminate\Support\Facades\Auth;
class AuthenticateOnceWithBasicAuth
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, $next)
{
return Auth::onceBasic() ?: $next($request);
}
}
接下里,我們在路由中使用該中介軟體:
Route::get('api/user', function () {
// 只有認證使用者才能進來
})->middleware('auth.basic.once');
新增自定義 Guard 驅動
你可以使用 Auth
門面的 extend
方法自定義認證 Guard 驅動(需要傳遞 provider
),這是在服務提供者中註冊的。比如,你就可以在現有的 AuthServiceProvider
中註冊。
<?php
namespace App\Providers;
use App\Services\Auth\JwtGuard;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* Register any application authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Auth::extend('jwt', function ($app, $name, array $config) {
// Return an instance of Illuminate\Contracts\Auth\Guard...
return new JwtGuard(Auth::createUserProvider($config['provider']));
});
}
}
可以看到,extend
方法的回撥函式裡返回的是 lluminate\Contracts\Auth\Guard
的一個實現。Guard
這個介面裡定義的方法,在你的自定義 Guard 驅動中都必須實現。之後,你就可以在 auth.php
配置檔案中的 guards
選項中使用它了。
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
新增自定義使用者 Provider
如果你不是使用關係型資料庫儲存使用者資料,就需要實現自定義使用者 Provider 驅動。這裡會用到 Auth
門面的 provider
方法:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Auth;
use App\Extensions\RiakUserProvider;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* Register any application authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Auth::provider('riak', function ($app, array $config) {
// Return an instance of Illuminate\Contracts\Auth\UserProvider...
return new RiakUserProvider($app->make('riak.connection'));
});
}
}
然後就可以在 auth.php
配置檔案中使用這個 Provider 驅動了。不過,首先要定義一個使用這個驅動的 Provider。
'providers' => [
'users' => [
'driver' => 'riak',
],
],
然後在 guards
配置中使用這個 Provider。
UserProvider
介面
Illuminate\Contracts\Auth\UserProvider
的實現僅負責從持久化儲存裝置中獲取 Illuminate\Contracts\Auth\Authenticatable
實現,這些持久化裝置是指 MySQL,Riak 之類的系統。這兩個介面允許 Laravel 認證機制,在無論使用者資料如何被儲存或使用什麼型別的類來表示它的情況下,繼續執行。
我們來看下 Illuminate\Contracts\Auth\UserProvider
介面:
<?php
namespace Illuminate\Contracts\Auth;
interface UserProvider {
public function retrieveById($identifier);
public function retrieveByToken($identifier, $token);
public function updateRememberToken(Authenticatable $user, $token);
public function retrieveByCredentials(array $credentials);
public function validateCredentials(Authenticatable $user, array $credentials);
}
retrieveById
方法接收的是使用者主鍵,比如 MySQL 資料庫裡的自增 ID。最後返回匹配這個 ID 的 Authenticatable
的實現。
retrieveByToken
方法接收的是使用者主鍵和「記住我」令牌(對應 remember_token
欄位值)。和 retrieveById
方法一樣,Authenticatable
的實現。
updateRememberToken
方法用新的 $token
值,更新 $user
的 remember_token
欄位值。這個新的 $token
值可以來源於使用者再次「記住我」的登入操作,或者是在登出賬號時產生的。
retrieveByCredentials
接收的是一個陣列,這個陣列資料最終是傳遞給 Auth::attempt
方法的認證欄位資料。這個方法會使用 where
條件查詢子句,使用 $credentials['username']
的值在持久化裝置裡查詢,並且返回一個 Authenticatable
實現。此方法不應嘗試進行任何密碼驗證或驗證。
validateCredentials
會比較 $user
和給定的 $credentials
陣列來認證使用者,此方法會使用 Hash::check
來比較 $user->getAuthPassword()
與 $credentials['password']
的值,並且返回一個布林值 true
或者 false
,表示提供的認證資料是否正確。
Authenticatable
介面
現在我們來看 UserProvider
中的方法,先來看 Authenticatable
介面的方法。記住,Provider 應該從 retrieveById
和 retrieveByCredentials
方法中實現這個介面,
<?php
namespace Illuminate\Contracts\Auth;
interface Authenticatable {
public function getAuthIdentifierName();
public function getAuthIdentifier();
public function getAuthPassword();
public function getRememberToken();
public function setRememberToken($value);
public function getRememberTokenName();
}
這個介面比較簡單,getAuthIdentifierName
返回使用者主鍵欄位名;getAuthIdentifier
方法使用者主鍵值。以 MySQL 作為後臺資料庫說明,主鍵就是自增主鍵;getAuthPassword
返回使用者的雜湊密碼。此介面允許身份驗證系統與任何 User 類一起使用,無論您正在使用什麼 ORM 或儲存抽象層。Laravel 內建的 App\User
模型就實現了這個介面,所以你可以用它作為的實現參考例子。
事件
在認證過程中,會觸發許多事件,你可以在 EventServiceProvider
中為這些事件新增監聽者。
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
'Illuminate\Auth\Events\Registered' => [
'App\Listeners\LogRegisteredUser',
],
'Illuminate\Auth\Events\Attempting' => [
'App\Listeners\LogAuthenticationAttempt',
],
'Illuminate\Auth\Events\Authenticated' => [
'App\Listeners\LogAuthenticated',
],
'Illuminate\Auth\Events\Login' => [
'App\Listeners\LogSuccessfulLogin',
],
'Illuminate\Auth\Events\Failed' => [
'App\Listeners\LogFailedLogin',
],
'Illuminate\Auth\Events\Logout' => [
'App\Listeners\LogSuccessfulLogout',
],
'Illuminate\Auth\Events\Lockout' => [
'App\Listeners\LogLockout',
],
'Illuminate\Auth\Events\PasswordReset' => [
'App\Listeners\LogPasswordReset',
],
];
本作品採用《CC 協議》,轉載必須註明作者和本文連結