完整的用户身份验证和访问控制 - 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 实现身份验证。 我们期待下面的评论。