Laravel 文件閱讀:認證

zhangbao發表於2017-09-27

翻譯、衍生自:https://learnku.com/docs/laravel/5.5/authenticatio...

想要快速開始?在全新的 Laravel 專案中執行 Artisan 命令 php artisan make:authphp 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 地址,這個地址是在 LoginControllerRegisterControllerResetPasswordController 中的 redirectTo 屬性中設定的,你可以修改它,以滿足你的需求。

protected $redirectTo = '/';

如果在跳轉時,還有一些通用的跳轉邏輯要處理的話,那麼就定義成一個 redirectTo 方法:

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

redirectTo() 的優先順序高於 redirectTo 屬性。

自定義認證欄位

Laravel 預設使用 email 作為認證關鍵欄位,如果要自定義,在 LoginController 中定義一個 username 方法即可。例如,下面我們將 name(使用者名稱)欄位作為認證關鍵欄位:

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

自定義 Guard

你也可以在認證和註冊使用者時,使用自定義「Guard」。然後在 LoginControllerRegisterControllerResetPasswordController 中的 guard 方法裡使用它(還方法返回一個 Guard 例項物件)。

use Illuminate\Support\Facades\Auth;

protected function guard()
{
    return Auth::guard('guard-name');
}

自定義驗證/儲存

如果你需要修改註冊頁面的表單欄位,或者要自定義儲存使用者到資料庫裡的邏輯,就需要修改 RegisterController 了。

RegisterControllervalidator 方法驗證從前臺註冊頁面傳遞過來的表單欄位,這裡定義了對應欄位的取值規則。你也可以按照需要修改這裡定義的規則。

RegisterControllercreate 方法用來在資料庫中建立一個新的 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.phpguards 陣列選項中的 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 值,更新 $userremember_token 欄位值。這個新的 $token 值可以來源於使用者再次「記住我」的登入操作,或者是在登出賬號時產生的。

retrieveByCredentials 接收的是一個陣列,這個陣列資料最終是傳遞給 Auth::attempt 方法的認證欄位資料。這個方法會使用 where 條件查詢子句,使用 $credentials['username'] 的值在持久化裝置裡查詢,並且返回一個 Authenticatable 實現。此方法不應嘗試進行任何密碼驗證或驗證

validateCredentials 會比較 $user 和給定的 $credentials 陣列來認證使用者,此方法會使用 Hash::check 來比較 $user->getAuthPassword()$credentials['password'] 的值,並且返回一個布林值 true 或者 false,表示提供的認證資料是否正確。

Authenticatable 介面

現在我們來看 UserProvider 中的方法,先來看 Authenticatable 介面的方法。記住,Provider 應該從 retrieveByIdretrieveByCredentials 方法中實現這個介面,

<?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 協議》,轉載必須註明作者和本文連結

相關文章