用戶認證
用戶認證
用戶認證
引言
{tip} 想要快速開始嗎? 只要在一個新的 Laravel 應用程序中運行
php artisan make:auth
和php artisan migrate
就可以了。 然后,把你的瀏覽器導航到http://your-app.test/register
或者其他任意一個分配給你的程序的 URL。這兩個命令將負責構建整個認證系統(tǒng)!
Laravel 使得實現身份驗證非常簡單。 事實上,幾乎所有的配置都是現成的。 身份驗證配置文件位于 config/auth.php
, 其中包含幾個有良好文檔記錄的選項,用于調整身份驗證服務的行為。
在其核心,Laravel 的認證設施由 “警衛(wèi)” 和 “提供者” 組成。守衛(wèi)決定如何對每個請求的用戶進行身份驗證。比如,Laravel 帶有一個 session
保護,它使用會話存儲和 Cookies 來維護狀態(tài)。
提供者決定如何從持久儲存中檢索用戶。 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} 如果應用程序不需要注冊,可以通過刪除新創(chuàng)建的
RegisterController
并修改路由聲明來禁用它:Auth::routes(['register' => false]);
。
視圖
如前一節(jié)所述, php artisan make:auth
命令將創(chuàng)建認證所需的所有視圖,并將它們放在 resources/views/auth
目錄中。
make:auth
命令還將創(chuàng)建一個包含應用程序基本布局的 resources/views/layouts
目錄。所有這些視圖都使用了 Bootstrap CSS 框架,但是你可以自由地定制它們。
認證
現在已經給認證的控制器設置好了路由和視圖,你可以在應用中注冊和認證新用戶了!因為控制器已經默認包含了驗證用戶是否存在和保存用戶到數據庫中的認證邏輯(通過 traits 實現的),現在你已經可以在瀏覽器中訪問應用了。
自定義路徑
當用戶認證成功,他們會被重定向到 /home
這個 URI 下。你可以在 LoginController
,RegisterController
, ResetPasswordController
,還有 VerificationController
控制器中定義 redirectTo
屬性來自定義驗證后的重定向位置:
protected $redirectTo = '/';
接下,你應該修改 RedirectIfAuthenticated
中間件中的 handle
方法,以便在重定向用戶時重定向到新的 URI。
如果重定向路徑需要自定義生成邏輯,你可以定義 redirectTo
方法替代 redirectTo
屬性:
protected function redirectTo(){ return '/path'; }
{提示} redirectTo 方法優(yōu)先于 redirectTo 屬性。
自定義用戶名
Laravel 默認使用 email
字段來認證。如果你想使用其他的字段,可以在 LoginController
控制器里面定義一個 username
方法:
public function username(){ return 'username'; }
自定義看守器
你還可以自定義用戶認證和注冊的 「看守器」。要實現這一功能,需要在 LoginController
,RegisterController
和 ResetPasswordController
中定義 guard
方法。該方法會返回一個看守器實例:
use Illuminate\Support\Facades\Auth;protected function guard(){ return Auth::guard('guard-name'); }
自定義驗證 / 存儲
為了修改新用戶在注冊時所需要填寫的表單字段,或者自定義如何將新用戶存儲到數據庫中,你可以修改 RegisterController
類。該類負責驗證和創(chuàng)建新用戶。
RegisterController
類的 validator
方法包含了驗證新用戶的規(guī)則,你可以隨心所欲地自定義該方法。
RegisterController
的 create
方法負責使用 Eloquent ORM 在數據庫中創(chuàng)建新的 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} 在這些例子中,
訪問指定的看守器實例
可以使用 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,這意味著此方法有助于構建一個無狀態(tài) 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}]
無狀態(tài) 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 請求的自定義身份驗證系統(tǒng)的最簡單方法,是使用 Auth::viaRequest
方法。此方法允許您使用單個閉包來快速定義身份驗證過程。
首先,在 AuthServiceProvider
的 boot
方法中調用 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', ], ],
添加自定義用戶提供器
如果不使用傳統(tǒng)的關系數據庫存儲用戶,就需要使用自己的身份驗證用戶提供器擴展 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 等持久化存儲系統(tǒng)中提取 Illuminate\Contracts\Auth\Authenticatable
實現。無論用戶如何存儲及用于表示它的類是什么類型,這兩個接口都允許 Laravel 身份驗證機制繼續(xù)運行:
我們來看看 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
更新 $user
的 remember_token
列。在「記住我」登錄校驗成功或者用戶登出時分配「刷新令牌」。
在嘗試登錄到應用時,retrieveByCredentials
方法接受憑證數組傳遞給 Auth::attempt
方法。此方法在底層持久化存儲中「查詢」與這些憑證匹配的用戶。通常,此方法運行一個基于 $credentials['username']
的 「where」 條件,它應該返回一個 Authenticatable
實現。此方法不就嘗試進行任何密碼校驗或身份驗證。
validateCredentials
方法應該比較給定的 $user
與 $credentials
來驗證用戶身份。例如,此方法或許應該使用 Hash::check
來比較 $user->getAuthPassword()
的值與 $credentials['password']
的值。它應該返回 true
或 false
,以表明用戶密碼是否有效。
身份驗證契約
我們已經剖析了 UserProvider
的每個方法。下面再來看看 Authenticatable
契約。切記,用戶提供器的 retrieveById
、 retrieveByToken
和 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
方法應該返回用戶的哈希密碼。此接口允許身份驗證系統(tǒng)與任一 User 類一直工作,不管使用的是哪種 ORM 或抽象存儲層。默認情況下,Laravel 的 app
目錄會包含一個實現了此接口的 User
類,你可以以這個實現示例作為參考。
事件
在身份驗證處理過程中 Laravel 引發(fā)了多種 事件 。 可以在 EventServiceProvider
中附著這些事件的監(jiān)聽器:
/** * 應用的事件監(jiān)聽器映射。 * * @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', ], ];