* * * * *
[TOC]
## 簡介
> {tip}?**想要快速起步?**?在一個全新的 Laravel 應用中運行?`php artisan make:auth`?和?`php artisan migrate`?命令,然后可以用瀏覽器訪問?`http://your-app.dev/register`?或者你在程序中定義的其他 url。這個兩個簡單的命令就可以搭建好整個認證系統的腳手架。
Laravel 中實現用戶認證非常簡單。實際上,幾乎所有東西都已經為你配置好了。配置文件位于?`config/auth.php`,其中包含了用于調整認證服務行為的、標注好注釋的選項配置。
在其核心代碼中,Laravel 的認證組件由?`guards`?和?`providers`?組成,Guard 定義了用戶在每個請求中如何實現認證,例如,Laravel 通過?`session`?guard 來維護 Session 存儲的狀態和 Cookie。
Provider 定義了如何從持久化存儲中獲取用戶信息,Laravel 底層支持通過 Eloquent 和數據庫查詢構建器兩種方式來獲取用戶,如果需要的話,你還可以定義額外的 Provider。
如果看到這些名詞覺得很困惑,大可不必太過擔心,因為對絕大多數應用而言,只需使用默認認證配置即可,不需要做什么改動。
### 數據庫注意事項
默認的 Laravel 在?`app`?文件夾中會含有?`App\User`?[Eloquent 模型](Eloquent 入門.md)。這個模型將使用默認的 Eloquent 認證來驅動。如果你的應用程序沒有使用 Eloquent,請選擇使用 Laravel 查詢構造器的?`database`?認證驅動。
為?`App\User`?模型創建數據庫表結構時,確認密碼字段最少必須 60 字符長。保持字段原定的 255 字符長是個好選擇。
`users`?數據表中必須含有 nullable 、100 字符長的?`remember_token`?字段。當用戶登錄應用并勾選「記住我」時,這個字段將會被用來保存「記住我」 session 的令牌。
## 認證快速入門
Laravel 帶有幾個預設的認證控制器,它們被放置在?`App\Http\Controllers\Auth`?命名空間內,`RegisterController`?處理用戶注冊,`LoginController`?處理用戶認證,`ForgotPasswordController`?處理重置密碼的 e-mail 鏈接,`ResetPasswordController`?包含重置密碼的邏輯。
這些控制器使用了 trait 來包含所需要的方法,對于大多數的應用程序而言,你并不需要修改這些控制器。
### 路由
Laravel 通過運行如下命令可快速生成認證所需要的路由和視圖:
~~~
php artisan make:auth
~~~
該命令應該在新安裝的應用下使用,它會生成 layout 布局視圖,注冊和登錄視圖,以及所有的認證路由,同時生成?`HomeController`?,用來處理登錄成功后會跳轉到該控制器下的請求。
### 視圖
正如上面所提到的,`php artisan make:auth`?命令會在?`resources/views/auth`?目錄下創建所有認證需要的視圖。
`make:auth`?命令還創建了?`resources/views/layouts`?目錄,該目錄下包含了應用的基礎布局文件。所有這些視圖都基于 Bootstrap CSS 框架,你也可以根據需要對其進行自定義。
### 認證
現在你已經為自帶的認證控制器設置好了路由和視圖,接下來我們來實現新用戶注冊和登錄認證。你可以在瀏覽器中訪問定義好的路由,認證控制器已經(通過 trait)包含了注冊及登錄邏輯。
#### 自定義路徑
當一個用戶成功進行登錄認證后,默認將會跳轉到?`/home`,你可以通過在?`LoginController`,`RegisterController`?和?`ResetPasswordController`?中設置?`redirectTo`?屬性來自定義登錄認證成功之后的跳轉路徑:
~~~
protected $redirectTo = '/';
~~~
如果跳轉路徑需要自定義邏輯來生成,你可以定義?`redirectTo`?方法來代替?`redirectTo`?屬性:
~~~
protected function redirectTo()
{
return '/path';
}
~~~
> {tip}?`redirectTo`?方法優先于?`redirectTo`?屬性。
#### 自定義用戶名
Laravel默認使用?`email`?字段來認證。如果你想用其他字段認證,可以在?`LoginController`?里面定義一個?`username`?方法
~~~
public function username()
{
return 'username';
}
~~~
#### 自定義 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`?方法負責使用?[Eloquent ORM](Eloquent 入門.md)?在數據庫中創建新的?`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
{
/**
* Update the user's profile.
*
* @param Request $request
* @return Response
*/
public function update(Request $request)
{
// $request->user() 返回認證過的用戶的實例...
}
}
~~~
#### 檢查用戶是否登錄
使用?`Auth`?facade 的?`check`?方法來檢查用戶是否登錄,如果已經登錄,將會返回?`true`:
~~~
use Illuminate\Support\Facades\Auth;
if (Auth::check()) {
// 這個用戶已經登錄...
}
~~~
> {tip} 盡管可以使用?`check`?方法來檢查用戶是否登錄,在允許該用戶訪問特定的路由或控制器之前,可以使用中間件來檢查用戶是否認證過。要想得到更多信息,請閱讀?[限制路由訪問](用戶認證.md)?的文檔。
### 限制路由訪問
[路由中間件](中間件.md)?用于限定認證過的用戶訪問指定的路由,Laravel 提供了?`auth`?中間件來達到這個目的,而這個中間件被定義在?`Illuminate\Auth\Middleware\Authenticate`?中。因為這個中間件已經在 HTTP kernel 中注冊了,只需要將它應用到路由定義中即可使用:
~~~
Route::get('profile', function () {
// 只有認證過的用戶能進來這里...
})->middleware('auth');
~~~
如果使用?[控制器類](控制器.md),可以在構造器中調用?`middleware`?方法,來代替在路由中直接定義:
~~~
public function __construct()
{
$this->middleware('auth');
}
~~~
#### 指定一個Guard
添加?`auth`?中間件到路由后,還需要指定使用哪個 guard 來實現認證。指定的 guard 對應配置文件?`auth.php`?中?`guards`?數組的某個鍵:
~~~
public function __construct()
{
$this->middleware('auth:api');
}
~~~
### 登錄限流
Laravel 內置的?`LoginController`?類提供?`Illuminate\Foundation\Auth\ThrottlesLogins`?trait 允許你在應用程序中限制登錄次數。默認情況下,如果用戶在進行幾次嘗試后仍不能提供正確的憑證,將在一分鐘內無法進行登錄。這個限制會特別針對用戶的用戶名稱 / 郵件地址和他們的 IP 地址。
## 手動認證用戶
當然,不一定要使用 Laravel 內置的認證控制器。如果選擇刪除這些控制器,可以直接調用 Laravel 的認證類來實現用戶認證管理。不用擔心,很簡單。
我們可以利用?`Auth`?[facade](Facades.md)?來訪問 Laravel 的認證服務,因此需要確認在類的頂部導入?`Auth`?facade。接下來讓我們看一下?`Auth`?的?`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' => $password])) {
// Authentication passed...
return redirect()->intended('dashboard');
}
}
}
~~~
`attempt`?方法會接受一個數組來作為第一個參數,這個數組的值可用來尋找數據庫里的用戶數據,所以在上面的例子中,用戶通過?`email`?字段被取出,如果用戶被找到了,數據庫里經過哈希的密碼將會與數組中哈希的?`password`?值比對,如果兩個值一樣的話就會開啟一個通過認證的 session 給用戶。
如果認證成功,`attempt`?方法將會返回?`true`,反之則為?`false`。
重定向器上的?`intended`?方法將會重定向用戶回原本想要進入的頁面,也可以傳入一個回退 URI 至這個方法,以避免要轉回的頁面不可使用。
#### 指定額外條件
可以加入除用戶的郵箱及密碼外的額外條件進行認證查找。例如,我們要確認用戶是否被標示為?`active`:
~~~
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
// The user is active, not suspended, and exists.
}
~~~
> {note} 在這些例子中,`email`?不是一個一定要有的選項,它僅僅是被用來當作例子,你可以用任何字段,只要它在數據庫的意義等同于「用戶名」。
#### 訪問指定 Guard 實例
可以通過?`Auth`?facade 的?`guard`?方法來指定使用特定的 guard 實例。這樣可以實現在應用不同部分管理用戶認證時使用完全不同的認證模型或者用戶表。
傳遞給?`guard`?方法 guard 名稱必須是?`auth.php`?配置文件中 guards 的值之一:
~~~
if (Auth::guard('admin')->attempt($credentials)) {
//
}
~~~
#### 注銷用戶
要想讓用戶注銷,你可以使用?`Auth`?facade 的?`logout`?方法。這個方法會清除所有認證后加入到用戶 session 的數據:
~~~
Auth::logout();
~~~
## 記住用戶
如果你想要提供「記住我」的功能,你需要傳入一個布爾值到?`attempt`?方法的第二個參數,在用戶注銷前 session 值都會被一直保存。`users`?數據表一定要包含一個?`remember_token`?字段,這是用來保存「記住我」令牌的。
~~~
if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
// 這個用戶被記住了...
}
~~~
> {tip} 如果使用 Laravel 內置的?`LoginController`,合適的「記住我」邏輯已經通過 traits 實現。
可以使用?`viaRemember`?方法來檢查這個用戶是否使用「記住我」 cookie 來做認證:
~~~
if (Auth::viaRemember()) {
//
}
~~~
### 其它認證方法
#### 用「用戶實例」做認證
如果你需要使用存在的用戶實例來登錄,你需要調用?`login`?方法,并傳入使用實例,這個對象必須是由?`Illuminate\Contracts\Auth\Authenticatable`?[contract](Contracts.md)?所實現。當然,`App/User`?模型已經實現了這個接口:
~~~
Auth::login($user);
// 登錄并且「記住」用戶
Auth::login($user, true);
~~~
你也可以指定 guard 實例:
~~~
Auth::guard('admin')->login($user);
~~~
#### 通過用戶 ID 做認證
使用?`loginUsingId`?方法來登錄指定 ID 用戶,這個方法接受要登錄用戶的主鍵:
~~~
Auth::loginUsingId(1);
// 登錄并且「記住」用戶
Auth::loginUsingId(1, true);
~~~
#### 僅在本次認證用戶
可以使用?`once`?方法來針對一次性認證用戶,沒有任何的 session 或 cookie 會被使用,這個對于構建無狀態的 API 非常的有用,`once`?方法跟?`attempt`?方法擁有同樣的傳入參數:
~~~
if (Auth::once($credentials)) {
//
}
~~~
## HTTP 基礎認證
[HTTP 基礎認證](http://en.wikipedia.org/wiki/Basic_access_authentication)?提供一個快速的方法來認證用戶,不需要任何「登錄」頁面。開始之前,先增加?`auth.basic`?[中間件](中間件.md)到你的路由,`auth.basic`?中間件已經被包含在 Laravel 框架中,所以你不需要定義它:
~~~
Route::get('profile', function () {
// 只有認證過的用戶可進入...
})->middleware('auth.basic');
~~~
一旦中間件被增加到路由上,當使用瀏覽器進入這個路由時,將自動的被提示需要提供憑證。默認情況下,`auth.basic`?中間件將會使用用戶的?`email`?字段當作「用戶名」。
#### FastCGI 的注意事項
如果是正在使用 FastCGI,則 HTTP 的基礎認證可能無法正常運作,你需要將下面這幾行加入你?`.htaccess`?文件中:
~~~
RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
~~~
### 無狀態 HTTP 基礎認證
你可以使用 HTTP 基礎認證而不用在 session 中設置用戶認證用的 cookie,這個功能對 API 認證來說非常有用。為了達到這個目的,[定義一個中間件](中間件.md)?并調用?`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);
}
}
~~~
接著,[注冊這個路由中間件](中間件.md),然后將它增加在一個路由上:
~~~
Route::get('api/user', function () {
// 只有認證過的用戶可以進入...
})->middleware('auth.basic.once');
~~~
## 增加自定義的 Guard
你可以使用?`Auth`?的?`extend`?方法來自定義認證 Guard,你需要在?[服務提供者](服務提供者.md)?中放置此代碼調用。因為 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
{
/**
* 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`?方法傳參進去的回調需要返回?`Illuminate\Contracts\Auth\Guard`?的實現,這個接口類有幾個方法你需要實現。定制好 Guard 以后,你需要在配置信息中開啟使用:
~~~
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
~~~
## 添加自定義用戶提供者
如果你沒有使用傳統的關系型數據庫存儲用戶信息,則需要使用自己的認證用戶提供者來擴展 Laravel。我們使用?`Auth`facade 上的?`provider`?方法定義自定義該提供者:
~~~
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Auth;
use App\Extensions\RiakUserProvider;
use Illuminate\Support\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'));
});
}
}
~~~
通過?`provider`?方法注冊用戶提供者后,你可以在配置文件?`config/auth.php`?中切換到新的用戶提供者。首先,在該配置文件定義一個使用新驅動的?`providers`?數組:
~~~
'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`?contract:
~~~
<?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。`Authenticatable`?的實現通過 ID 匹配的方法來取出和返回。
`retrieveByToken`?函數借助用戶唯一的?`$identifier`?和「記住我」`$token`?來獲取用戶。如同之前的方法,`Authenticatable`?的實現應該被返回。
`updateRememberToken`?方法使用新的?`$token`?更新了?`$user`?的?`remember_token`?字段。這個新的令牌可以是全新的令牌(當使用「記住我」嘗試登錄成功時),或是 null(當用戶注銷時)。
`retrieveByCredentials`?方法獲取了從?`Auth::attempt`?方法發送過來的憑證數組(當想要登錄時)。這個方法應該要 「查找」所使用的持久化存儲系統來匹配這些憑證。通常,這個方法會運行一個帶著「where」`$credentials['username']`?條件的查找。這個方法接著需要返回一個?`UserInterface`?的實現。**此方法不應該企圖做任何密碼驗證或認證操作。**
`validateCredentials`?方法需要比較?`$user`?和?`$credentials`?來認證這個用戶。例如,這個方法可能會使用?`Hash::check`?比較?`$user->getAuthPassword()`?字符串及?`$credentials['password']`。這個方法通過返回一個布爾值來驗證密碼是否正確。
### 用戶認證 Contract
現在我們已經介紹了?`UserProvider`?的每個方法,讓我們看一下?`Authenticate`?contract。這個提供者需要?`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`?應該要返回用戶哈希后的密碼。這個接口允許認證系統和任何用戶類運作,不用管你在使用何種 ORM 或存儲抽象層。默認情況下,Laravel 的?`app`?文件夾中會包含?`User`?類來實現此接口,所以你可以觀察這個類以作為實現的例子。
## 事件
Laravel 提供了在認證過程中的各種?[事件](事件系統.md)。你可以在?`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',
],
];
~~~
- 前言
- 翻譯說明
- 發行說明
- 升級說明
- 貢獻導引
- 入門指南
- 安裝
- 配置信息
- 文件夾結構
- 請求周期
- 開發環境部署
- Homestead
- Valet
- 核心概念
- 服務容器
- 服務提供者
- Facades
- Contracts
- HTTP層
- 路由
- 中間件
- CSRF 保護
- 控制器
- 請求
- 響應
- 視圖
- Session
- 表單驗證
- 前端
- Blade 模板
- 本地化
- 前端指南
- 編輯資源 Mix
- 安全
- 用戶認證
- Passport OAuth 認證
- 用戶授權
- 加密解密
- 哈希
- 重置密碼
- 綜合話題
- Artisan 命令行
- 廣播系統
- 緩存系統
- 集合
- 錯誤與日志
- 事件系統
- 文件存儲
- 輔助函數
- 郵件發送
- 消息通知
- 擴展包開發
- 隊列
- 任務調度
- 數據庫
- 快速入門
- 查詢構造器
- 分頁
- 數據庫遷移
- 數據填充
- Redis
- Eloquent ORM
- 快速入門
- 模型關聯
- Eloquent 集合
- 修改器
- 序列化
- 測試
- 快速入門
- HTTP 測試
- 瀏覽器測試 Dusk
- 數據庫測試
- 測試模擬器
- 官方擴展包
- Cashier 交易工具包
- Envoy 部署工具
- Scout 全文搜索
- Socialite 社會化登錄