完整的用戶身份驗證和訪問控制 - 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 創建用戶認證控制器

現在我們要創建具有loginregister功能的身份驗證控制器。

首先,我們將運行:

 php artisan make:controller Auth/ApiAuthController

現在我們將一些類導入到文件app/Http/Controllers/Auth/ApiAuthController.php中。 這些類將用於創建loginregister功能。 我們將通過添加以下內容來導入類:

 use App\User; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str;

…到控制器的頂部。

現在,要為我們的用戶添加 Laravel API 身份驗證,我們將在同一個文件中創建loginlogoutregister (註冊)函數。

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); }

之後,我們需要將loginregisterlogout函數添加到我們的路由中,即在 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,並刪除該類中的createedit方法。 之後,我們將在剩餘的每個函數中添加以下代碼,稍作編輯:

 $response = ['message' => '<function name> function']; return response($response, 200);

我們將根據需要填寫<function name> 。 例如, update函數將這個作為它的主體:

 $response = ['message' => 'update function']; return response($response, 200);

手動 Laravel 身份驗證測試:創建用戶

要註冊用戶,我們將使用以下參數向/api/register發送POST請求: nameemail (必須是唯一的)、 passwordpassword_confirmation

使用 Postman 向 /api/register 發送 POST 請求的屏幕截圖。

創建用戶時,API 將返回一個令牌,我們將在進一步的請求中將其用作身份驗證的手段。

要登錄,我們將向/api/login發送一個POST請求。 如果我們的憑證是正確的,我們也會以這種方式從我們的 Laravel 登錄 API 中獲取一個令牌。

使用 Postman 向 /api/login 發送 POST 請求的屏幕截圖。

當我們想要訪問受保護的路由時,我們可以使用從該請求返回的授權令牌。 在 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 [ // ]; } }

要使用這個新通知,我們需要重寫UserAuthenticatable類繼承的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中,我們需要更新updown函數以分別添加和刪除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.adminapi.superAdmin等,視情況而定。

這就是創建我們的中間件所需的全部內容。

把它們放在一起

為了測試我們的身份驗證和訪問控制是否正常工作,還需要執行一些額外的步驟。

測試 Laravel 身份驗證和訪問控制:第 1 步

我們需要修改ArticleControllerindex函數並註冊路由。 (在實際項目中,我們會使用 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 步

現在我們可以嘗試在沒有身份驗證令牌的情況下訪問路由。 我們應該收到一個身份驗證錯誤。

使用 Postman 向 /api/articles 發送 GET 請求的屏幕截圖,並收到一條消息“未驗證”作為響應。

測試 Laravel 身份驗證和訪問控制:步驟 3

我們還可以嘗試使用授權令牌(我們在本文前面的註冊或登錄中獲得的)訪問相同的路由。

有時,這可能會導致類似於以下的錯誤:

 Unknown column 'api_token' in 'where clause' (SQL: select * from `users` where `api_token` = ... 

使用 Postman 獲取 api/articles 路由的未知列錯誤的屏幕截圖。

如果發生這種情況,開發人員應確保已運行 Passport 遷移並在config/auth.php ['guards']['api']['driver']設置為passport

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

之後,配置緩存也需要更新。

一旦解決了這個問題,我們應該可以訪問該路線。

使用 Postman 向 /api/articles 發送 GET 請求的屏幕截圖,帶有正常的 JSON 響應。

測試 Laravel 身份驗證和訪問控制:步驟 4

是時候測試訪問控制了。 讓我們將->middleware('api.admin')附加到文章路由,如下所示:

 Route::get('/articles', 'ArticleController@index')->middleware('api.admin')->name('articles');

正如我們可以通過api/user路由看到的那樣,我們使新創建的用戶自動分配類型 0。

使用 Postman 向 /api/user 發送 GET 請求的屏幕截圖。響應包括 ID、姓名、電子郵件、空電子郵件驗證時間戳、填寫的創建和更新時間戳以及類型。

因此,我們應該以這樣的用戶身份嘗試訪問articles端點時出錯。

使用 Postman 向 /api/articles 發送 GET 請求的屏幕截圖,其中包含 Permission Denied JSON 響應。

出於測試的目的,我們將數據庫中的用戶修改為type 1。再次通過api/user路由驗證更改後,我們準備再次嘗試GET /articles/路由。

使用 Postman 作為 Laravel 認證用戶向 /api/articles 發送 GET 請求的屏幕截圖,與之前的請求相同,只是這個請求的響應時間是 1,280 毫秒而不是 890 毫秒。

它完美地工作。

製作更複雜應用程序的開發人員應該注意,適當的訪問控制不會這麼簡單。 在這種情況下,可以使用其他第三方應用程序或 Laravel 的門和策略來實現自定義用戶訪問控制。 在本系列的第二部分,我們將介紹更強大、更靈活的訪問控制解決方案。

Laravel API 身份驗證:我們學到了什麼

在這個 Laravel Passport 教程中,我們討論了:

  1. 創建一個虛擬控制器和模型以在測試我們的 Laravel Passport 示例時使用一些東西。
  2. 創建使我們的 API 順利運行所必需的中間件,解決 CORS 並強制 API 始終返回 JSON 響應。
  3. 設置基本的 Laravel API 身份驗證:註冊、登錄和註銷。
  4. 基於 Laravel 的默認設置“密碼重置”功能。
  5. 創建訪問控制中間件,為不同的路由添加用戶授權權限級別。

對於在 Laravel 開發服務領域工作的任何人來說,這些都是必不可少的技能。 讀者將在這個 GitHub 存儲庫中找到最終結果,現在應該可以很好地使用 Laravel 實現身份驗證。 我們期待下面的評論。