完整的用戶身份驗證和訪問控制 - Laravel Passport 教程,Pt。 1
已發表: 2022-03-11在開發 Web 應用程序時,通常最好將其分成兩層。 中間層 API 與數據庫交互,而 Web 層通常由前端 SPA 或 MPA 組成。 這樣,Web 應用程序的耦合更加鬆散,從長遠來看更易於管理和調試。
創建 API 後,在無狀態 API 上下文中設置身份驗證和狀態似乎有些問題。
在本文中,我們將了解如何使用 Laravel 和 Passport 在 API 中實現完整的用戶身份驗證和簡單形式的訪問控制。 你應該有使用 Laravel 的經驗,因為這不是一個介紹性教程。
安裝前提:
- PHP 7+、MySQL 和 Apache(想要同時安裝這三個版本的開發人員可以使用 XAMPP。)
- 作曲家
- 拉拉維爾 7
- Laravel 護照。 由於 API 通常是無狀態的並且不使用會話,因此我們通常使用令牌來保持請求之間的狀態。 Laravel 使用 Passport 庫來實現一個完整的 OAuth2 服務器,我們可以在我們的 API 中使用它來進行身份驗證。
- Postman、cURL 或 Insomnia 來測試 API——這取決於個人喜好
- 您選擇的文本編輯器
- Laravel 助手(適用於 Laravel 6.0 及更高版本)——安裝 Laravel 和 Passport 後,只需運行:
composer require laravel/helpers
安裝了以上內容後,我們就可以開始了。 確保通過編輯.env
文件來設置數據庫連接。
Laravel Passport 教程,第 1 步:為虛擬請求添加控制器和模型
首先,我們將為虛擬請求創建一個控制器和模型。 該模型在本教程中不會有太大用處,它只是讓您了解控制器要操作的數據。
在創建模型和控制器之前,我們需要創建一個遷移。 在終端或cmd.exe
窗口中,如果您使用的是 Windows,請運行:
php artisan make:migration create_articles_table --create=articles
現在,轉到database/migrations
文件夾並打開名稱類似於xxxx_xx_xx_xxxxxx_create_articles_table.php
的文件。
在類的up
函數中,我們將這樣寫:
Schema::create('articles', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->string('body'); $table->integer('user_id'); $table->timestamps(); });
接下來,我們將創建一個Article
模型。 為此,請運行:
php artisan make:model Article
然後我們通過運行創建ArticleController
控制器:
php artisan make:controller ArticleController --resource
接下來,我們將編輯文件app/Providers/AppServiceProvider.php
並通過添加以下內容導入Illuminate\Support\Facades\Schema
類:
use Illuminate\Support\Facades\Schema
…到文件頂部導入的底部。
然後,在boot
函數中,我們將編寫:
Schema::defaultStringLength(191);
完成所有這些後,我們可以運行:
php artisan migrate
…應用我們在上面創建的遷移。
Laravel Passport 教程,第 2 步:創建必要的中間件
在這裡,我們將添加 API 工作所需的中間件。
JSON 響應
需要的第一部分是ForceJsonResponse
中間件,它將自動將所有響應轉換為 JSON。
為此,請運行:
php artisan make:middleware ForceJsonResponse
這是該中間件的句柄函數,在App/Http/Middleware/ForceJsonReponse.php
:
public function handle($request, Closure $next) { $request->headers->set('Accept', 'application/json'); return $next($request); }
接下來,我們將中間件添加到$routeMiddleware
數組中的app/Http/Kernel.php
文件中:
'json.response' => \App\Http\Middleware\ForceJsonResponse::class,
然後,我們還將它添加到同一文件中的$middleware
數組中:
\App\Http\Middleware\ForceJsonResponse::class,
這將確保ForceJsonResponse
中間件在每個請求上運行。
CORS(跨域資源共享)
為了允許我們的 Laravel REST API 的使用者從不同的來源訪問它,我們必須設置 CORS。 為此,我們將創建一個名為Cors
的中間件。
在終端或命令提示符下, cd
進入項目根目錄並運行:
php artisan make:middleware Cors
然後,在app/Http/Middleware/Cors.php
中,添加以下代碼:
public function handle($request, Closure $next) { return $next($request) ->header('Access-Control-Allow-Origin', '*') ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') ->header('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, X-Token-Auth, Authorization'); }
要加載這個中間件,我們需要在app/Http/Kernel.php
的$routeMiddleware
數組中添加一行:
'cors' => \App\Http\Middleware\Cors::class,
此外,我們必須將它添加到$middleware
數組中,就像我們為之前的中間件所做的那樣:
\App\Http\Middleware\Cors::class,
之後,我們將把這個路由組附加到routes/api.php
:
Route::group(['middleware' => ['cors', 'json.response']], function () { // ... });
我們所有的 API 路由都將進入該函數,如下所示。
Laravel Passport 教程,第 3 步:為 API 創建用戶認證控制器
現在我們要創建具有login
和register
功能的身份驗證控制器。
首先,我們將運行:
php artisan make:controller Auth/ApiAuthController
現在我們將一些類導入到文件app/Http/Controllers/Auth/ApiAuthController.php
中。 這些類將用於創建login
和register
功能。 我們將通過添加以下內容來導入類:
use App\User; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str;
…到控制器的頂部。
現在,要為我們的用戶添加 Laravel API 身份驗證,我們將在同一個文件中創建login
、 logout
和register
(註冊)函數。
register
函數將如下所示:
public function register (Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:6|confirmed', ]); if ($validator->fails()) { return response(['errors'=>$validator->errors()->all()], 422); } $request['password']=Hash::make($request['password']); $request['remember_token'] = Str::random(10); $user = User::create($request->toArray()); $token = $user->createToken('Laravel Password Grant Client')->accessToken; $response = ['token' => $token]; return response($response, 200); }
login
功能是這樣的:
public function login (Request $request) { $validator = Validator::make($request->all(), [ 'email' => 'required|string|email|max:255', 'password' => 'required|string|min:6|confirmed', ]); if ($validator->fails()) { return response(['errors'=>$validator->errors()->all()], 422); } $user = User::where('email', $request->email)->first(); if ($user) { if (Hash::check($request->password, $user->password)) { $token = $user->createToken('Laravel Password Grant Client')->accessToken; $response = ['token' => $token]; return response($response, 200); } else { $response = ["message" => "Password mismatch"]; return response($response, 422); } } else { $response = ["message" =>'User does not exist']; return response($response, 422); } }
最後, logout
功能:
public function logout (Request $request) { $token = $request->user()->token(); $token->revoke(); $response = ['message' => 'You have been successfully logged out!']; return response($response, 200); }
之後,我們需要將login
、 register
和logout
函數添加到我們的路由中,即在 API 中已經存在的路由組中:
Route::group(['middleware' => ['cors', 'json.response']], function () { // ... // public routes Route::post('/login', 'Auth\ApiAuthController@login')->name('login.api'); Route::post('/register','Auth\ApiAuthController@register')->name('register.api'); Route::post('/logout', 'Auth\ApiAuthController@logout')->name('logout.api'); // ... });
最後,我們需要將HasApiToken
特徵添加到User
模型中。 導航到app/User
並確保您擁有:
use HasApiTokens, Notifiable;
......在班上名列前茅。
到目前為止我們所擁有的……
如果我們啟動應用服務器——即運行php artisan serve
serve——然後嘗試向路由/api/user
發送GET
請求,我們應該會收到以下消息:
{ "message": "Unauthenticated." }
這是因為我們沒有通過身份驗證來訪問該路由。 為了保護您選擇的某些路由,我們可以將它們添加到routes/api.php
中的Route::post
行之後:
Route::middleware('auth:api')->group(function () { // our routes to be protected will go in here });
在繼續之前,我們將 logout 路由添加到auth:api
中間件,因為 Laravel 使用一個令牌來註銷用戶——一個無法從auth:api
中間件外部訪問的令牌。 我們的公共路線如下所示:
Route::group(['middleware' => ['cors', 'json.response']], function () { // ... // public routes Route::post('/login', 'Auth\ApiAuthController@login')->name('login.api'); Route::post('/register', 'Auth\ApiAuthController@register')->name('register.api'); // ... });
另一方面,我們的受保護路線如下所示:
Route::middleware('auth:api')->group(function () { // our routes to be protected will go in here Route::post('/logout', 'Auth\ApiAuthController@logout')->name('logout.api'); });
現在我們將導航到我們在app/Http/Controllers/ArticleController.php
ArticleController
創建的 ArticleController,並刪除該類中的create
和edit
方法。 之後,我們將在剩餘的每個函數中添加以下代碼,稍作編輯:
$response = ['message' => '<function name> function']; return response($response, 200);
我們將根據需要填寫<function name>
。 例如, update
函數將這個作為它的主體:
$response = ['message' => 'update function']; return response($response, 200);
手動 Laravel 身份驗證測試:創建用戶
要註冊用戶,我們將使用以下參數向/api/register
發送POST
請求: name
、 email
(必須是唯一的)、 password
和password_confirmation
。
創建用戶時,API 將返回一個令牌,我們將在進一步的請求中將其用作身份驗證的手段。
要登錄,我們將向/api/login
發送一個POST
請求。 如果我們的憑證是正確的,我們也會以這種方式從我們的 Laravel 登錄 API 中獲取一個令牌。
當我們想要訪問受保護的路由時,我們可以使用從該請求返回的授權令牌。 在 Postman 中,“授權”選項卡有一個下拉菜單,可以將類型設置為“Bearer Token”,之後令牌可以進入令牌字段。
這個過程在失眠症中非常相似。
cURL 用戶可以通過傳遞參數-H "Authorization: Bearer <token>"
來執行等效操作,其中<token>
是從登錄或註冊響應中給出的授權令牌。
與 cURL 一樣,如果開發人員計劃使用 axios 或此類庫來使用 API,他們可以添加一個Authorization
標頭,其值為Bearer <token>
。
Laravel Passport 教程,第 4 步:創建密碼重置功能
現在基本身份驗證已經完成,是時候設置密碼重置功能了。
為此,我們可以選擇創建一個api_auth
控制器目錄,創建新的自定義控制器,並實現該功能; 或者我們可以編輯我們可以使用 Laravel 生成的身份驗證控制器。 在這種情況下,我們將編輯身份驗證控制器,因為整個應用程序是一個 API。

首先,我們將通過運行以下命令生成身份驗證控制器:
composer require laravel/ui php artisan ui vue --auth
我們將編輯app/Http/Controllers/Auth/ForgotPasswordController.php
中的類,添加這兩個方法:
protected function sendResetLinkResponse(Request $request, $response) { $response = ['message' => "Password reset email sent"]; return response($response, 200); } protected function sendResetLinkFailedResponse(Request $request, $response) { $response = "Email could not be sent to this email address"; return response($response, 500); }
接下來,我們需要設置實際重置密碼的控制器,因此我們將導航到app/Http/Controllers/Auth/ResetPasswordController.php
並覆蓋默認函數,如下所示:
protected function resetPassword($user, $password) { $user->password = Hash::make($password); $user->save(); event(new PasswordReset($user)); } protected function sendResetResponse(Request $request, $response) { $response = ['message' => "Password reset successful"]; return response($response, 200); } protected function sendResetFailedResponse(Request $request, $response) { $response = "Token Invalid"; return response($response, 401); }
我們還需要在控制器中導入一些類,方法是添加:
use Illuminate\Auth\Events\PasswordReset; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash;
…到控制器的頂部。
我們還想修改使用哪個電子郵件通知,因為 Laravel 附帶的郵件通知不使用 API 令牌進行授權。 我們可以通過運行以下命令在app/Notifications
下創建一個新的:
php artisan make:notification MailResetPasswordNotification
我們需要編輯文件app/Notifications/MailResetPasswordNotification.php
看起來像這樣:
<?php namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Auth\Notifications\ResetPassword; use Illuminate\Support\Facades\Lang; class MailResetPasswordNotification extends ResetPassword { use Queueable; protected $pageUrl; public $token; /** * Create a new notification instance. * * @param $token */ public function __construct($token) { parent::__construct($token); $this->pageUrl = 'localhost:8080'; // we can set whatever we want here, or use .env to set environmental variables } /** * Get the notification's delivery channels. * * @param mixed $notifiable * @return array */ public function via($notifiable) { return ['mail']; } /** * Get the mail representation of the notification. * * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) { if (static::$toMailCallback) { return call_user_func(static::$toMailCallback, $notifiable, $this->token); } return (new MailMessage) ->subject(Lang::getFromJson('Reset application Password')) ->line(Lang::getFromJson('You are receiving this email because we received a password reset request for your account.')) ->action(Lang::getFromJson('Reset Password'), $this->pageUrl."?token=".$this->token) ->line(Lang::getFromJson('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.users.expire')])) ->line(Lang::getFromJson('If you did not request a password reset, no further action is required.')); } /** * Get the array representation of the notification. * * @param mixed $notifiable * @return array */ public function toArray($notifiable) { return [ // ]; } }
要使用這個新通知,我們需要重寫User
從Authenticatable
類繼承的sendPasswordResetNotification
方法。 我們需要做的就是將它添加到app/User.php
:
public function sendPasswordResetNotification($token) { $this->notify(new \App\Notifications\MailResetPasswordNotification($token)); }
通過正常運行的郵件設置,此時通知應該可以正常工作。
現在剩下的就是用戶訪問控制。
Laravel Passport 教程,第 5 步:創建訪問控制中間件
在我們創建訪問控制中間件之前,我們需要更新user
表以有一個名為type
的列,用於確定用戶級別:type 0 是普通用戶,type 1 是 admin,type 2 是超級管理員。
要更新user
表,我們必須通過運行以下命令來創建遷移:
php artisan make:migration update_users_table_to_include_type --table=users
在新創建的表單database/migrations/[timestamp]_update_users_table.php
中,我們需要更新up
和down
函數以分別添加和刪除type
列:
public function up() { Schema::table('users', function (Blueprint $table) { $table->integer('type'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropIfExists('type'); }); }
接下來,我們將運行php artisan migrate
。 完成後,我們必須在ApiAuthController.php
文件中編輯我們的register
函數,將其添加到$user = User::create($request->toArray());
行之前。 :
$request['type'] = $request['type'] ? $request['type'] : 0;
此外,我們需要將此行添加到$validator
數組中:
'type' => 'integer',
這兩個編輯中的第一個將默認使所有註冊用戶成為“普通用戶”,即,如果沒有輸入用戶類型。
訪問控制中間件本身
現在我們可以創建兩個用於訪問控制的中間件:一個用於管理員,一個用於超級管理員。
所以我們將運行:
php artisan make:middleware AdminAuth php artisan make:middleware SuperAdminAuth
首先,我們將導航到app/Http/Middleware/AdminAuth.php
並導入Illuminate\Support\Facades\Auth
,然後像這樣編輯handle
函數:
public function handle($request, Closure $next) { if (Auth::guard('api')->check() && $request->user()->type >= 1) { return $next($request); } else { $message = ["message" => "Permission Denied"]; return response($message, 401); } }
我們還需要編輯app/Http/Middleware/SuperAdminAuth.php
中的handle
函數:
public function handle($request, Closure $next) { if (Auth::guard('api')->check() && $request->user()->type >= 2) { return $next($request); } else { $message = ["message" => "Permission Denied"]; return response($message, 401); } }
您還應該通過添加以下內容在兩個文件的頂部導入Auth
類:
use Illuminate\Support\Facades\Auth;
…到那裡找到的進口商品的底部。
為了使用我們的新中間件,我們將在內核中引用這兩個類——即,在app/Http/Kernel.php
——通過將以下行添加到$routeMiddleware
數組中:
'api.admin' => \App\Http\Middleware\AdminAuth::class, 'api.superAdmin' => \App\Http\Middleware\SuperAdminAuth::class,
如果開發人員想在給定的路由中使用中間件,您只需將其添加到路由函數中,如下所示:
Route::post('route','Controller@method')->middleware('<middleware-name-here>');
<middleware-name-here>
在這種情況下可以是api.admin
、 api.superAdmin
等,視情況而定。
這就是創建我們的中間件所需的全部內容。
把它們放在一起
為了測試我們的身份驗證和訪問控制是否正常工作,還需要執行一些額外的步驟。
測試 Laravel 身份驗證和訪問控制:第 1 步
我們需要修改ArticleController
的index
函數並註冊路由。 (在實際項目中,我們會使用 PHPUnit 並將其作為自動化測試的一部分。在這裡,我們手動添加了一個用於測試目的的路由——它可以在之後被刪除。)
我們將導航到app/Http/Controllers/ArticleController
中的ArticleController
控制器,並將index
函數修改為如下所示:
public function index() { $response = ['message' => 'article index']; return response($response, 200); }
接下來,我們將通過轉到routes/api.php
文件並附加以下內容在路由中註冊該函數:
Route::middleware('auth:api')->group(function () { Route::get('/articles', 'ArticleController@index')->name('articles'); });
測試 Laravel 身份驗證和訪問控制:第 2 步
現在我們可以嘗試在沒有身份驗證令牌的情況下訪問路由。 我們應該收到一個身份驗證錯誤。
測試 Laravel 身份驗證和訪問控制:步驟 3
我們還可以嘗試使用授權令牌(我們在本文前面的註冊或登錄中獲得的)訪問相同的路由。
有時,這可能會導致類似於以下的錯誤:
Unknown column 'api_token' in 'where clause' (SQL: select * from `users` where `api_token` = ...
如果發生這種情況,開發人員應確保已運行 Passport 遷移並在config/auth.php
['guards']['api']['driver']
設置為passport
:
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ],
之後,配置緩存也需要更新。
一旦解決了這個問題,我們應該可以訪問該路線。
測試 Laravel 身份驗證和訪問控制:步驟 4
是時候測試訪問控制了。 讓我們將->middleware('api.admin')
附加到文章路由,如下所示:
Route::get('/articles', 'ArticleController@index')->middleware('api.admin')->name('articles');
正如我們可以通過api/user
路由看到的那樣,我們使新創建的用戶自動分配類型 0。
因此,我們應該以這樣的用戶身份嘗試訪問articles
端點時出錯。
出於測試的目的,我們將數據庫中的用戶修改為type
1。再次通過api/user
路由驗證更改後,我們準備再次嘗試GET
/articles/
路由。
它完美地工作。
製作更複雜應用程序的開發人員應該注意,適當的訪問控制不會這麼簡單。 在這種情況下,可以使用其他第三方應用程序或 Laravel 的門和策略來實現自定義用戶訪問控制。 在本系列的第二部分,我們將介紹更強大、更靈活的訪問控制解決方案。
Laravel API 身份驗證:我們學到了什麼
在這個 Laravel Passport 教程中,我們討論了:
- 創建一個虛擬控制器和模型以在測試我們的 Laravel Passport 示例時使用一些東西。
- 創建使我們的 API 順利運行所必需的中間件,解決 CORS 並強制 API 始終返回 JSON 響應。
- 設置基本的 Laravel API 身份驗證:註冊、登錄和註銷。
- 基於 Laravel 的默認設置“密碼重置”功能。
- 創建訪問控制中間件,為不同的路由添加用戶授權權限級別。
對於在 Laravel 開發服務領域工作的任何人來說,這些都是必不可少的技能。 讀者將在這個 GitHub 存儲庫中找到最終結果,現在應該可以很好地使用 Laravel 實現身份驗證。 我們期待下面的評論。