NO IMAGE

簡介

注:想要快速上手?只需要在新安裝的 Laravel 應用下執行 php artisan make:auth 和 php artisan migrate,這兩個命令會生成使用者登入註冊所需要的所有東西,然後在瀏覽器中訪問 http://your-app.dev/register 即可。

Laravel 中實現使用者認證非常簡單。實際上,幾乎所有東西 Laravel 都已經為你配置好了。配置檔案位於 config/auth.php,其中包含了用於調整認證服務行為的、文件友好的選項配置。

在底層程式碼中,Laravel 的認證元件由“guards”和“providers”組成,Guard 定義了使用者在每個請求中如何實現認證,例如,Laravel 通過 session guard 來維護 Session 儲存的狀態和 Cookie。

Provider 定義瞭如何從持久化儲存中獲取使用者資訊,Laravel 底層支援通過 Eloquent 和資料庫查詢構建器兩種方式來獲取使用者,如果需要的話,你還可以定義額外的 Provider。

如果看到這些名詞覺得雲裡霧裡,大可不必太過擔心,因為對絕大多數應用而言,只需使用預設認證配置即可,不需要做什麼改動。

學院君註解:通俗點說,在進行使用者認證的時候,要做兩件事,一個是從資料庫存取使用者資料,一個是把使用者登入狀態儲存起來,在 Laravel 的底層實現中,通過 Provider 存取資料,通過 Guard 儲存使用者認證資訊,前者主要和資料庫打交道,後者主要和 Session 打交道(API 例外)。

資料庫考量

預設情況下,Laravel 在 app 目錄下包含了一個 Eloquent 模型App\User,這個模型可以和預設的 Eloquent 認證驅動一起使用。如果你的應用不使用 Eloquent,你可以使用 database 認證驅動,該驅動使用 Laravel 查詢構建器與資料庫互動。

為 App\User 模型構建資料庫表結構的時候,確保 password 欄位長度至少有60位。保持預設字串長度(255)是個不錯的選擇。

還有,你需要驗證 users 表包含了 remember_token,該欄位是個可以為空的字串型別,欄位長度為100,用於在登入時儲存應用維護的“記住我” Session 令牌。

快速入門

Laravel 提供了幾個預置的認證控制器,位於 App\Http\Controllers\Auth 名稱空間下, RegisterController 用於處理新使用者註冊, LoginController 用於處理使用者登入認證, ForgotPasswordController 用於處理重置密碼郵件連結, ResetPasswordController 包含重置密碼邏輯,每個控制器都使用 trait 來引入它們需要的方法。對很多應用而言,你根本不需要修改這些控制器:

路由

Laravel 通過執行如下命令可快速生成認證所需要的路由和檢視:

php artisan make:auth

新安裝的應用執行該命令會生成佈局、註冊和登入檢視,以及所有的認證路由,同時生成 HomeController 用於處理應用的登入請求。

開啟 routes/web.php 路由檔案會發現新增了兩行:

登入註冊相關路由都定義在上面 Auth::routes() 方法內。

檢視

正如上面所提到的,php artisan make:auth 命令會在 resources/views/auth 目錄下建立所有認證需要的檢視。

make:auth 命令還建立了 resources/views/layouts 目錄,該目錄下包含了應用的基礎佈局檔案。所有這些檢視都使用了 Bootstrap CSS 框架,你也可以根據需要對其進行自定義。

認證

現在你已經為自帶的認證控制器設定好了路由和檢視,接下來我們來實現新使用者註冊和登入認證。你可以在瀏覽器中訪問定義好的路由,認證控制器預設已經包含了註冊及登入邏輯(通過trait)。

我們先來註冊一個新使用者,在瀏覽器中訪問 http://laravel55.dev/register,即可進入註冊頁面:

填寫表單點選『Register』按鈕即可完成註冊。註冊成功後頁面跳轉到認證後的頁面 http://laravel55.dev/home

要測試登入功能,可以先退出當前使用者,然後訪問登入頁面 http://laravel55.dev/login

使用我們之前註冊的資訊登入成功後,同樣會跳轉到 http://laravel55.dev/home

自定義路徑

我們已經知道,當一個使用者成功進行登入認證後,預設將會跳轉到 /home,你可以通過在 LoginController, RegisterController 和 ResetPasswordController 中定義 redirectTo 屬性來自定義登入認證成功之後的跳轉路徑:

protected $redirectTo = '/';

如果重定向路徑需要自定義生成邏輯可以定義一個 redirectTo 方法來取代 redirectTo 屬性:

protected function redirectTo()
{
return '/path';
}

注:redirectTo 方法優先順序大於redirectTo 屬性。

自定義使用者名稱

預設情況下,Laravel 使用 email 欄位進行認證,如果你想要自定義認證欄位,可以在 LoginController 中定義 username 方法:

public function username()
{
return 'username';
}

自定義Guard

你還可以自定義用於實現使用者註冊登入的“guard”,要實現這一功能,需要在 LoginControllerRegisterController和 ResetPasswordController 中定義 guard 方法,該方法將會返回一個 guard 例項:

use Illuminate\Support\Facades\Auth;
protected function guard()
{
return Auth::guard('guard-name');
}

需要注意的是,“guard”名稱需要在配置檔案 config/auth.php 中配置過:

自定義驗證/儲存

要修改新使用者註冊所必需的表單欄位,或者自定義新使用者欄位如何儲存到資料庫,你可以修改 RegisterController類。該類負責為應用驗證輸入引數和建立新使用者。

RegisterController 的 validator 方法包含了新使用者註冊的驗證規則,你可以按需要自定義該方法。

RegisterController 的 create 方法負責使用 Eloquent ORM 在資料庫中建立新的 App\User 記錄。當然,你也可以基於自己的需要自定義該方法。

獲取登入使用者

你可以通過 Auth 門面訪問認證使用者:

use Illuminate\Support\Facades\Auth;
// 獲取當前認證使用者...
$user = Auth::user();
// 獲取當前認證使用者的ID...
$id = Auth::id();

此外,使用者通過認證後,你還可以通過 Illuminate\Http\Request 例項訪問認證使用者(型別提示類會通過依賴注入自動注入到控制器方法中):

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ProfileController extends Controller{
/**
* 更新使用者屬性.
*
* @param  Request  $request
* @return Response
*/
public function update(Request $request)
{
// $request->user() 返回認證使用者例項...
}
}

上面兩種方式返回的結果完全一致:

判斷當前使用者是否通過認證

要判斷某個使用者是否登入到應用,可以使用 Auth 門面的 check 方法,如果使用者通過認證則返回 true

use Illuminate\Support\Facades\Auth;
if (Auth::check()) {
// The user is logged in...
}

注:儘管我們可以使用 check 方法判斷使用者是否通過認證,但是我們通常的做法是在使用者訪問特定路由/控制器之前使用中介軟體來驗證使用者是否通過認證,想要了解更多,可以檢視下面的路由保護。

路由保護

路由中介軟體可用於只允許通過認證的使用者訪問給定路由。Laravel 通過定義在 Illuminate\Auth\Middleware\Authenticate 中的 auth 中介軟體來實現這一功能。由於該中介軟體已經在 HTTP kernel 中註冊,你所要做的僅僅是將該中介軟體加到相應的路由定義中:

Route::get('profile', function() {
// 只有認證使用者可以進入...
})->middleware('auth');

當然,如果你也可以在控制器的構造方法中呼叫 middleware 方法而不是在路由器中直接定義實現同樣的功能:

public function __construct(){
$this->middleware('auth');
}

比如我們的 HomeController 就是這麼做的:

如果我們在沒有登入的情況下訪問 http://laravel55.dev/home 頁面就會重定向到登入頁面。

指定一個Guard

新增 auth 中介軟體到路由後,還可以指定使用哪個 guard 來實現認證, 指定的 guard 對應配置檔案 config/auth.php中 guards 陣列的某個鍵 :

public function __construct()
{
$this->middleware('auth:api');
}

如果沒有指定的話,預設 guard 是 web,這也是配置檔案中配置的:

登入失敗次數限制

如果你使用了 Laravel 自帶的 LoginController 類, 就已經啟用了內建的 Illuminate\Foundation\Auth\ThrottlesLogins trait 來限制使用者登入失敗次數。預設情況下,使用者在幾次登入失敗後將在一分鐘內不能登入,這種限制基於使用者的使用者名稱/郵箱地址 IP地址作為唯一鍵。

手動認證使用者

當然,你也可以不使用 Laravel 自帶的認證控制器。如果你選擇移除這些控制器,需要直接使用 Laravel 認證類來管理使用者認證。別擔心,這很簡單!

我們可以通過 Auth 門面來訪問認證服務,因此我們需要確保在類的頂部匯入了 Auth 門面,接下來,讓我們看看如何通過 attempt 方法實現登入認證:

<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
/**
* 處理登入認證
*
* @return Response
* @translator laravelacademy.org
*/
public function authenticate()
{
if (Auth::attempt(['email' => $email, 'password' => $password])) {
// 認證通過...
return redirect()->intended('dashboard');
}
}
}

attempt 方法接收鍵/值對作為第一個引數,陣列中的值被用於從資料表中查詢對應使用者。在上面的例子中,將會通過 email 的值作為查詢條件去資料庫獲取對應使用者,如果使用者被找到,經雜湊運算後儲存在資料庫中的密碼將會和傳遞過來的經雜湊運算處理的密碼值進行比較。如果兩個經雜湊運算的密碼相匹配,那麼將會為這個使用者設定一個認證 Session,標識該使用者登入成功。感興趣的同學可以去看下底層原始碼實現邏輯:

如果認證成功的話 attempt 方法將會返回 true。否則,返回 false

重定向器上的 intended 方法將使用者重定向到登入之前使用者想要訪問的 URL,在目標 URL 無效的情況下回退 URI 將會傳遞給該方法。

指定額外條件

如果需要的話,除了使用者郵件和密碼之外還可以在認證查詢時新增額外的條件,例如,我們可以驗證被標記為有效的使用者:

if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
// The user is active, not suspended, and exists.
}

這裡的實現原理是在查詢使用者記錄時,只是排除了陣列中的密碼欄位,其他欄位都會作為查詢條件之一進行篩選:

注:在這些例子中,並不僅僅限於使用 email 進行登入認證,這裡只是作為演示示例,你可以將其修改為資料庫中任何其他可用作“username”的欄位。

訪問指定 Guard 例項

你可以使用 Auth 門面的 guard 方法指定想要使用的 guard 例項,這種機制允許你在同一個應用中對不同的認證模型或使用者表實現完全獨立的使用者認證。

該功能可用於為不同表的不同型別使用者(同一個表不同型別使用者理論上也可以)實現隔離式登入提供了方便,我們只要為每張表配置一個獨立的 guard 就可以了。比如我們除了 users 表之外還有一張 admins 表用於存放後臺管理員,要實現管理員的單獨登入,就可以這麼配置 auth.php 配置檔案:

'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
]
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Admin::class,
],
],

友情提示:新建的用於登入認證的模型類需要繼承 Illuminate\Foundation\Auth\User 基類,不然後面就會出現不能認證的窘況。

傳遞給 guard 方法的 guard 名稱對應配置檔案 auth.php 中 guards 配置的 admin 鍵:

if (Auth::guard('admin')->attempt($credentials)) {
//
}

需要注意的是使用這種方式認證的使用者在後續操作需要傳遞 guard 時也要傳遞相匹配的 guard,比如上面提到的 auth中介軟體,對應的呼叫方式也要調整(在路由中使用也是一樣):

$this->middleware('auth:admin'); 

獲取使用者時也是一樣:

Auth::guard('admin')->user();

退出

要退出應用,可以使用 Auth 門面的 logout 方法,這將會清除使用者 Session 中的認證資訊:

Auth::logout();

記住使用者

如果你想要在應用中提供“記住我”的功能,可以傳遞一個值為 true 的布林值作為第二個引數到 attempt 方法(不傳的話預設是 false),這樣使用者登入認證狀態就會一直保持直到他們手動退出。當然,你的 users 表必須包含 remember_token 欄位,該欄位用於儲存“記住我”令牌。

if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
// The user is being remembered...
}

注:如果你在使用自帶的 LoginController 控制器,相應的記住使用者邏輯已經通過控制器使用的 trait 實現了。

如果你在使用“記住”使用者功能,可以使用 viaRemember 方法來判斷使用者是否通過“記住我”Cookie進行認證:

if (Auth::viaRemember()) {
//
}

其它認證方法

認證一個使用者例項

如果你需要將一個已存在的使用者例項直接登入到應用,可以呼叫 Auth 門面的 login 方法並傳入使用者例項,傳入例項必須是 Illuminate\Contracts\Auth\Authenticatable 契約的實現,當然,Laravel 自帶的 App\User 模型已經實現了該介面:

Auth::login($user);
// 登入並 "記住" 給定使用者...
Auth::login($user, true);

當然,你可以指定想要使用的 guard 例項:

Auth::guard('admin')->login($user);

通過ID認證使用者

要通過使用者ID登入到應用,可以使用 loginUsingId 方法,該方法接收你想要認證使用者的主鍵作為引數:

Auth::loginUsingId(1);
// 登入並 "記住" 給定使用者...
Auth::loginUsingId(1, true);

一次性認證使用者

你可以使用 once 方法只在單個請求中將使用者登入到應用,而不儲存任何 Session 和 Cookie,這在構建無狀態的 API 時很有用:

if (Auth::once($credentials)) {
//
}

基於 HTTP 的基本認證

HTTP 基本認證能夠幫助使用者快速實現登入認證而不用設定專門的登入頁面,首先要在路由中加上 auth.basic 中介軟體。該中介軟體是 Laravel 自帶的,所以不需要自己定義:

Route::get('profile', function() {
// 只有認證使用者可以進入...
})->middleware('auth.basic');

中介軟體加到路由中後,當在瀏覽器中訪問該路由時,會自動提示需要認證資訊,預設情況下,auth.basic 中介軟體使用使用者記錄上的 email 欄位作為“使用者名稱”:

這種基本認證除了沒有獨立的登入表單檢視之外底層實現邏輯和正常的登入認證沒有區別。

FastCGI上的注意點

如果你使用 PHP FastCGI,HTTP 基本認證將不能正常工作,需要在 .htaccess 檔案加入如下內容:

RewriteCond %{HTTP:Authorization} ^(. )$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

無狀態的 HTTP 基本認證

你也可以在使用 HTTP 基本認證時不在 Session 中設定使用者標識 Cookie,這在 API 認證中非常有用。要實現這個功能,需要定義一個呼叫 onceBasic 方法的中介軟體。如果該方法沒有返回任何響應,那麼請求會繼續走下去:

<?php
namespace Illuminate\Auth\Middleware;
use Illuminate\Support\Facades\Auth;
class AuthenticateOnceWithBasicAuth
{
/**
* 處理輸入請求.
*
* @param  \Illuminate\Http\Request  $request
* @param  \Closure  $next
* @return mixed
* @translator laravelacademy.org
*/
public function handle($request, $next)
{
return Auth::onceBasic() ?: $next($request);
}
}

接下來,將 AuthenticateOnceWithBasicAuth 註冊到路由中介軟體並在路由中使用它:

Route::get('api/user', function() {
// 只有認證使用者可以進入...
})->middleware('auth.basic.once');

新增自定義 Guard 驅動

你可以通過 Auth 門面的 extend 方法定義自己的認證 guard 驅動,該功能需要在某個服務提供者的 boot 方法中實現,由於 Laravel 已經自帶了一個 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
{
/**
* 註冊任意應用認證/授權服務
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Auth::extend('jwt', function($app, $name, array $config) {
// 返回一個Illuminate\Contracts\Auth\Guard例項...
return new JwtGuard(Auth::createUserProvider($config['provider']));
}); 
}
}

正如你在上面例子中所看到的,傳遞給 extend 方法的閉包回撥需要返回 Illuminate\Contracts\Auth\Guard 的實現例項,該介面包含了自定義認證 guard 驅動需要的一些方法。定義好自己的認證 guard 驅動之後,就可以在配置檔案 auth.php 的 guards 配置中使用這個新的 guard 驅動:

'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],

新增自定義使用者提供者

如果你沒有使用傳統的關係型資料庫儲存使用者資訊,則需要使用自己的認證使用者提供者來擴充套件 Laravel。我們使用 Auth 門面上的 provider 方法定義自定義該提供者:

<?php
namespace App\Providers;
use Illuminate\Support\Facades\Auth;
use App\Extensions\RiakUserProvider;
use Illuminate\Support\ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* 註冊任意應用認證/授權服務.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Auth::provider('riak', function($app, array $config) {
// 返回一個Illuminate\Contracts\Auth\UserProvider例項...
return new RiakUserProvider($app->make('riak.connection'));
});
}
}

通過 provider 方法註冊使用者提供者後,你可以在配置檔案 config/auth.php 中切換到新的使用者提供者。首先,定義一個使用新驅動的 provider :

'providers' => [
'users' => [
'driver' => 'riak',
],
],

然後,可以在你的 guards 配置中使用這個提供者:

'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],

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 欄位中的“記住我”令牌獲取使用者。和上一個方法一樣,該方法也返回 Authenticatable 實現。

updateRememberToken 方法使用新的 $token 更新 $user 的 remember_token 欄位,新令牌可以是新生成的令牌(在登入是選擇“記住我”被成功賦值)或者null(使用者退出)。

retrieveByCredentials 方法在嘗試登入系統時獲取傳遞給 Auth::attempt 方法的認證資訊陣列。該方法接下來去底層持久化儲存系統查詢與認證資訊匹配的使用者,通常,該方法執行一個帶“where”條件($credentials[‘username’])的查詢。然後該方法返回 Authenticatable 的實現。這個方法不應該做任何密碼校驗和認證。

validateCredentials 方法比較給定 $user 和 $credentials 來認證使用者。例如,這個方法比較 $user->getAuthPassword() 字串和經 Hash::check 處理的 $credentials['password']。這個方法根據密碼是否有效返回布林值 true 或 false

Authenticatable 契約

既然我們已經探索了 UserProvider 上的每一個方法,接下來讓我們看看 Authenticatable。記住,提供者需要從 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 中這將是自增ID,getAuthPassword 返回經雜湊處理的使用者密碼,這個介面允許認證系統處理任何使用者類,不管是你使用的是 ORM 還是儲存抽象層。預設情況下,Laravel app 目錄下的 User 類實現了這個介面,所以你可以將這個類作為實現例子。

事件

Laravel 支援在認證過程中觸發多種事件,你可以在自己的 EventServiceProvider 中監聽這些事件:

/**
* 應用的事件監聽器對映.
*
* @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',
],
];