使用者認證


使用者認證

    引言
    • 資料庫注意事項
  • 使用者認證快速指南
    • 路由
    • 視圖
    • #認證
    • 檢索已驗證的使用者
    • 路徑保護
    • 登入限制
    ##手動驗證使用者
  • 記住使用者
    • 其他認證方法
    HTTP 基本認證
  • 無狀態HTTP 基本驗證
    #已登出
  • 其他裝置的會話無效
    」社會認證
  • 新增自訂保護
  • #關閉請求保護
    新增自訂使用者提供者
  • 使用者提供者合約
    • 可認證的合約

############### ##########

引言

{tip} 想要快速開始嗎? 只要在一個新的 Laravel 應用程式中執行 php artisan make:authphp artisan migrate 就可以了。然後,把你的瀏覽器導航到 http://your-app.test/register 或其他任意一個分配給你的程式的 URL。這兩個命令將負責建構整個認證系統!

Laravel 使得實作身份驗證非常簡單。事實上,幾乎所有的配置都是現成的。身份驗證設定檔位於 config/auth.php, 其中包含幾個具有良好文件記錄的選項,用於調整身份驗證服務的行為。

在其核心,Laravel 的認證設施由 “警衛” 和 “提供者” 組成。守衛決定如何對每個請求的使用者進行身份驗證。例如,Laravel 附帶一個 session 保護,它使用會話儲存和 Cookies 來維護狀態。

提供者決定如何從持久儲存中檢索使用者。 Laravel 支援使用 Eloquent 和資料庫查詢產生器檢索使用者。但是,你可以根據應用程式的需求來自由定義其他提供者。

如果這些聽起來讓你很困惑,別擔心!許多應用程式永遠不需要修改預設的身份驗證配置。

資料庫注意事項

#預設情況下, Laravel 包含一個App\User Eloquent model 在你的app 目錄下。這個模型可與預設的 Eloquent 身份驗證驅動程式一起使用。如果你的應用程式沒有使用 Eloquent,你可以用 database 身份驗證驅動程序,它用的是 Laravel 查詢產生器。

當為 App\User 模型產生資料庫架構時,請確保密碼的長度至少為 60 個字元。保持預設的字串長度為 255 個字元是一個不錯的選擇。

另外,你應該驗證 「users」(或等效)表格是否包含一個可空的,含有 100 個字元的 remember_token 字串。此列將用於儲存使用者登入應用程式時選擇 “記住我” 選項的令牌。

使用者認證快速指南

Laravel 附帶了幾個預先建置的驗證控制器,它們位於App\Http\Controllers\Auth 命名空間中。 RegisterController 處理新的用戶註冊,LoginController 處理身份驗證,ForgotPasswordController 處理用於重置密碼的電子郵件鏈接,ResetPasswordController 包含重設密碼的邏輯。這些控制器中的每一個都使用一個特性來包含它們的必要方法。對於許多應用程序,您根本不需要修改這些控制器。

路由

Laravel 提供了一個快速的方法,可以使用一個簡單的命令來建立認證所需的所有路由和視圖:

php artisan make:auth

此命令應該用於新應用程序,並將安裝佈局視圖、註冊和登入視圖以及所有驗證端點的路由。也會產生一個 HomeController 來處理應用程式儀表板的登入後請求。

{tip} 如果應用程式不需要註冊,可以透過刪除新建立的 RegisterController 並修改路由宣告來停用它:Auth::routes([' register' => false]);

檢視

#如前一節所述, php artisan make:auth 指令將建立認證所需的所有視圖,並將它們放在resources/views/auth 目錄中。

make:auth 指令也會建立一個包含應用程式基本佈局的 resources/views/layouts 目錄。所有這些視圖都使用了 Bootstrap CSS 框架,但你可以自由地自訂它們。

已認證

#現在已經為認證的控制器設定了路由和視圖,你可以在應用程式中註冊和認證新用戶了!因為控制器已經預設包含了驗證使用者是否存在和儲存使用者到資料庫中的認證邏輯(透過 traits 實現的),現在你已經可以在瀏覽器中存取應用程式了。

自訂路徑

當使用者認證成功,他們會被重定向到 /home 這個 URI 下。你可以在 LoginControllerRegisterControllerResetPasswordController,還有VerificationController 控制器中定義redirectTo 屬性來自訂驗證後的重定向位置:

protected $redirectTo = '/';

接下,你應該修改RedirectIfAuthenticated 中間件中的handle 方法,以便在重定向使用者時重定向到新的URI。

如果重定向路徑需要自訂產生邏輯,你可以定義redirectTo 方法替代redirectTo# 屬性:

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

{提示} redirectTo 方法優先於redirectTo 屬性。

自訂使用者名稱

Laravel 預設使用 email 欄位來認證。如果你想使用其他的字段,可以在 LoginController 控制器裡面定義一個 username 方法:

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

自訂看守器

你也可以自訂使用者認證和註冊的 「看守器」。要實現這項功能,需要在 LoginController,RegisterControllerResetPasswordController 中定義 guard 方法。此方法會傳回一個看守器實例:

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

自訂驗證/ 儲存

為了修改新使用者在註冊時所需要填寫的表單字段,或自訂如何將新使用者儲存到資料庫中,你可以修改 RegisterController 類別。該類別負責驗證和創建新用戶。

RegisterController 類別的 validator 方法包含了驗證新使用者的規則,你可以隨心所欲地自訂該方法。

RegisterControllercreate 方法負責使用  Eloquent ORM 在資料庫中建立新的 App\User 記錄。你可以根據資料庫的需要自訂該方法。

擷取認證用戶

#你可以透過Auth facade 來存取已認證的用戶:

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 facade 的check 方法來檢查使用者是否已認證。如果已認證,將會傳回true

use Illuminate\Support\Facades\Auth;
if (Auth::check()) {   
 // 用户已经登录了...
}

{提示} 雖然可以使用check 方法確認使用者是否被認證,但在允許使用者存取的某些路由/ 控制器之前,通常還是會使用中間件來驗證使用者是否進行過身份驗證。想要了解更多信息,請查看有關 保護路由 的文檔。

保護路由

#路由中間件可以用於只允許通過認證的使用者存取給定的路由。 Laravel 自帶了一個 auth 中間件,它定義在 Illuminate\Auth\Middleware\Authenticate 中。由於這個中間件已經在HTTP 核心中註冊,你只需把這個中間件附加到路由定義中:

Route::get('profile', function () {  
  // 只有认证过的用户可以进入...
 })->middleware('auth');

當然,如果你使用控制器,你可以在控制器的構造函數中呼叫 middleware 方法來直接將其附加到路由定義中:

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

重定向未認證的使用者

auth 中間件偵測到一個未認證使用者時,它會把使用者重新導向到名為login 的命名路由上。
您可以透過修改app/Http/Middleware/Authenticate.php 檔案中的redirectTo 函數來修改此行為:

/**
 * Get the path the user should be redirected to.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return string
 */
 protected function redirectTo($request){ 
    return route('login');
 }

指定看守器

當你把auth 中間件加入路由時,同時也能指定使用哪個看守器進行使用者認證。指定的看守器應該對應auth.php 設定檔中guards 陣列中的一個鍵:

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

登入限流

如果你使用Laravel 內建的LoginController 類,Illuminate\Foundation\Auth\ThrottlesLogins trait 已經包含在該控制器中了。預設情況下,如果使用者多次嘗試卻無法提供正確的登入憑證,那麼該使用者在一分鐘內將無法再次嘗試登入。這種限流策略是基於使用者的使用者名稱 / 郵件位址及其 IP 位址的唯一性。

手動驗證使用者

#不一定要在 Lavarel 中使用驗證控制器。如果選擇刪除這些控制器,就需要直接使用 Lavarel 驗證類別。別擔心,很容易!

可以使用 Auth facade 存取 Laravel 服務,因此需要在類別的開頭匯入  Auth 。下面來看看 attempt 方法:

<?php
    namespace App\Http\Controllers;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Auth;
    class LoginController extends Controller{  
     /**
     * 处理身份验证尝试。
     *
     * @param  \Illuminate\Http\Request $request
     *
     * @return Response
     */   
    public function authenticate(Request $request)   
     {       
        $credentials = $request->only('email', 'password');      
        if (Auth::attempt($credentials)) {        
            // 身份验证通过...           
           return redirect()->intended('dashboard');    
             }   
           }
        }

attempt 方法的每個參數是一個關聯陣列。數組值用於在資料庫中尋找使用者。在上面的範例中,將透過 email 列的值來尋找使用者。如果找到該用戶,將以儲存在資料庫中的雜湊密碼與陣列中的 password 值做比較。不需要對 password 做雜湊運算,框架在與資料庫中的雜湊密碼做比較前自動對此值做雜湊運算。如果兩個雜湊值匹配,將為該用戶建立驗證通過的 session。

如果驗證成功,attempt 方法傳回  true ,否則回傳 false

重定向中的 intended 方法將經由驗證中間件將使用者重新導向至驗證前截獲的 URL 。如果預期目標不存在,可以為此方法指定一個回退 URI 。

指定額外條件

除了使用者的電子郵件和密碼之外,還可以在身分驗證查詢中新增其他條件。例如, 我們可以驗證使用者是否已經被標記為「啟動」:

if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {  
  // 用户存在,已激活且未被禁用。
 }

{note} 在這些例子中, email 不是必須的選項,它只用來做示範。你應該使用與你的資料庫中 “用戶名” 對應的列名。

存取指定的看守器實例

可以使用 Auth facade 的 guard 方法指定想要使用的看守器實例。這允許你使用完全獨立的可驗證模型或使用者表來管理應用程式各個部分的驗證。

傳遞給guard 方法的看守器名稱需要與auth.php 配置中的配置項目之一相符:

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

登出

用戶登出需要使用Auth facade 的logout 方法。它會清除使用者會話(session)中的使用者驗證資訊:

Auth::logout();

#記住使用者

如果想在應用程式中提供「記住我」功能,可以給attempt 方法傳遞一個布林值作為其第二個參數,這會無限期地保持用戶身份驗證,直到用戶手動登出。使用者表需要包含字串類型的 remember_token 欄位用於儲存令牌。

if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) { 
   // 用户被记住...
}

{tip} 如果使用了 Laravel 內建的 LoginController,「記住」使用者的正確邏輯已經由控制器所使用的 traits 實作。

如果啟用了「記住使用者」,可以使用viaRemember 方法判斷是否使用了「記住我」cookie 對使用者做身份驗證:

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

其它驗證方法

#驗證使用者實例

如果要將已經存在的使用者登錄應用,可以呼叫login 方法,並以使用者實例作為其參數。該物件必須實作 Illuminate\Contracts\Auth\Authenticatable 契約 。 Laravel 自帶的App\User 模型已經實作了這個介面:

Auth::login($user);
// 登录并「记住」给定的用户...
Auth::login($user, true);

使用以下方式指定想要的看守器實例:

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

透過ID 驗證使用者身分

可以使用loginUsingId 方法透過ID 將使用者登入應用程式。這個方法接受希望驗證身分使用者的主鍵:

Auth::loginUsingId(1);
// 登录并「记住」给定用户...
Auth::loginUsingId(1, true);

僅驗證一次使用者身分

可以使用once 方法在單一請求中將用戶登入應用程式中。這樣做將不使用 session 或 cookies,這意味著此方法有助於建立一個無狀態 API:

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

##

HTTP 基礎認證

HTTP 基礎認證 提供了一種快速方法來驗證你應用程式中的用戶,而無需設定專用的「登入」頁面。開始之前, 先把 auth.basic 中間件 附加到你的路由中。 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 基礎驗證,而無需在會話中設定使用者識別碼cookie,這對API 的身份驗證特別有用。為此 ,請定義一個中間件 它將呼叫 onceBasic 方法。如果onceBasic 方法沒有回傳任何回應,那麼請求就可以進一步傳遞到應用程式:

<?php
  namespace App\Http\Middleware;
  use Illuminate\Support\Facades\Auth;
  class AuthenticateOnceWithBasicAuth{   
       /**
     * 处理传入的请求
     *
     * @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');

退出

要手動把使用者從應用程式中退出登錄,你可以使用Auth facade 上的logout 方法。這將清除使用者會話中的身份認證資訊:

use Illuminate\Support\Facades\Auth;Auth::logout();

#讓它裝置上的Session 失效

Laravel還提供了一種機制,用於將其它設備上的用戶Session 失效和“註銷”,而不會使其當前設備上的Session 失效。首先,你需要保證Illuminate\Session\Middleware\AuthenticateSession 中間件在你的app/Http/Kernel.php 類別中的web 中間件群組中,並且沒有被註解掉:

'web' => [   
    // ...
    \Illuminate\Session\Middleware\AuthenticateSession::class,  
    // ...
  ],

然後, 你就可以使用Auth facade 上的logoutOtherDevices 方法。此方法要求使用者提供其目前密碼,你的應用程式應透過輸入表單接受該密碼:

use Illuminate\Support\Facades\Auth;
Auth::logoutOtherDevices($password);

{note} 當呼叫logoutOtherDevices 方法後,使用者的其它Session將完全失效,這意味著他們將“退出”他們之前通過身份認證的所有看守器。

#

新增自訂的看守器

你可以使用 Auth facade 的 extend 方法來定義自己的驗證看守器。你應該在 服務提供器 中呼叫  extend 方法。由於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 介面的實例。這個介麵包含了一些你需要在自訂的看守器中實現的方法。當你的自訂看守器定義完成之後,你可以在auth.php 設定檔的guards 設定中使用這個看守器:

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

#請求閉包看守器

實作基於HTTP 請求的自訂身分驗證系統最簡單的方法,是使用Auth::viaRequest 方法。此方法可讓您使用單一閉包來快速定義身份驗證流程。

首先,在 AuthServiceProviderboot 方法中呼叫 Auth::viaRequest 方法。 viaRequest 方法接受一個看守器名稱作為其第一個參數。此名稱可以是描述你自訂看守器的任何字串。傳遞給該方法的第二個參數應該是一個閉包函數,它接收傳入的HTTP 請求並傳回一個使用者實例,或者,如果驗證失敗,則為null

use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
  /**
 * 注册任意应用认证/授权服务。
 *
 * @return void
 */
 public function boot(){  
   $this->registerPolicies();  
    Auth::viaRequest('custom-token', function ($request) {     
       return User::where('token', $request->token)->first();  
       });
     }

當你完成了自訂看守器後,就可以在auth.php 設定檔的guards 設定中使用這個看守器:

'guards' => [   
 'api' => [      
   'driver' => 'custom-token',  
   ],
 ],

#新增自訂用戶提供器

如果不使用傳統的關聯式資料庫儲存用戶,就需要使用自己的驗證用戶提供器擴充Lavarel 。可以使用Auth facade 的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) {         
            // 返回 Illuminate\Contracts\Auth\UserProvider 实例...            
            return new RiakUserProvider($app->make('riak.connection'));       
           });   
        }}

一旦使用provider 方法註冊完畢,就可以在auth.php 設定檔中切換到新的使用者提供者。先定義一個使用新驅動的provider

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

隨後就可以在guards 設定中使用這個提供者:

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

#

使用者提供器契約

Illuminate\Contracts\Auth\UserProvider 實作僅負責從MySQL、Riak 等持久化儲存系統中提取Illuminate\Contracts\Auth\Authenticatable 實作。無論使用者如何儲存及用於表示它的類別是什麼類型,這兩個介面都允許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 函數通常接受用於表示類別的key(如MySQL 資料庫中自動遞增的ID)作為參數,並且取得並傳回與這個ID 相符的Authenticatable 實作。

retrieveByToken 函數透過使用者的唯一 $identifier 和儲存在 remember_token 列的 「記得我」 令牌取得使用者。與前一方法相同,它會傳回 Authenticatable 實作。

updateRememberToken 方法以新  $token 更新  $userremember_token 欄位。在「記得我」登入校驗成功或使用者登出時指派「刷新令牌」。

在嘗試登入應用程式時,retrieveByCredentials 方法接受憑證陣列傳遞給 Auth::attempt 方法。此方法在底層持久化儲存中「查詢」與這些憑證相符的使用者。通常,此方法執行一個基於 $credentials['username'] 的 “where” 條件,它應該會傳回一個 Authenticatable 實作。 此方法不就嘗試進行任何密碼校驗或身份驗證。

validateCredentials 方法應該比較給定的 $user$credentials 來驗證使用者身分。例如,此方法或許應該使用Hash::check 來比較$user->getAuthPassword() 的值與$credentials['password'] 的值。它應該傳回 truefalse ,以表示使用者密碼是否有效。

身份驗證契約

我們已經剖析了 UserProvider 的每個方法。下面再來看看 Authenticatable 契約。切記,使用者提供器的 retrieveByIdretrieveByTokenretrieveByCredentials 方法將傳回此介面的實例:

<?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 類,你可以以這個實作範例作為參考。

事件

#在身分驗證處理過程中 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',   
         ],
      ];
本文章首發在 LearnKu.com 網站上。