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