Полная аутентификация пользователей и контроль доступа — руководство по Laravel Passport, Pt. 1
Опубликовано: 2022-03-11При разработке веб-приложения, как правило, рекомендуется разделить его на два уровня. API среднего уровня взаимодействует с базой данных, а веб-уровень обычно состоит из интерфейсного SPA или MPA. Таким образом, веб-приложение становится более слабо связанным, что упрощает управление и отладку в долгосрочной перспективе.
Когда API создан, настройка аутентификации и состояния в контексте API без сохранения состояния может показаться несколько проблематичной.
В этой статье мы рассмотрим, как реализовать полную аутентификацию пользователей и простую форму контроля доступа в API с использованием Laravel и Passport. У вас должен быть опыт работы с Laravel, так как это не вводное руководство.
Предварительные требования для установки:
- PHP 7+, MySQL и Apache (разработчики, желающие установить все три сразу, могут использовать XAMPP).
- Композитор
- Ларавель 7
- Паспорт Ларавеля. Поскольку 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); }
Затем мы добавим промежуточное ПО в наш файл app/Http/Kernel.php
в массиве $routeMiddleware
:
'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
, а затем попытаемся отправить GET
-запрос на маршрут /api/user
, мы должны получить сообщение:
{ "message": "Unauthenticated." }
Это потому, что мы не аутентифицированы для доступа к этому маршруту. Чтобы сделать некоторые маршруты по вашему выбору защищенными, мы можем добавить их в routes/api.php
сразу после строк Route::post
:
Route::middleware('auth:api')->group(function () { // our routes to be protected will go in here });
Прежде чем двигаться дальше, мы добавим маршрут выхода в промежуточное ПО 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'); });
Теперь мы перейдем к ArticleController
, который мы создали в app/Http/Controllers/ArticleController.php
и удалим методы create
и edit
в этом классе. После этого мы добавим в каждую оставшуюся функцию следующий фрагмент кода, слегка отредактированный:
$response = ['message' => '<function name> function']; return response($response, 200);
Мы заполним <function name>
соответствующим образом. Например, тело функции update
будет таким:
$response = ['message' => 'update function']; return response($response, 200);
Ручной тест аутентификации Laravel: создание пользователя
Чтобы зарегистрировать пользователя, мы отправим запрос POST
в /api/register
со следующими параметрами: name
, email
(который должен быть уникальным), password
и password_confirmation
.
Когда пользователь будет создан, API вернет токен, который мы будем использовать в дальнейших запросах в качестве средства аутентификации.
Чтобы войти, мы отправим POST
-запрос на /api/login
. Если наши учетные данные верны, мы также получим токен из нашего API входа в систему Laravel таким образом.
Токен авторизации, который мы получаем из этого запроса, мы можем использовать, когда хотим получить доступ к защищенному маршруту. В Postman на вкладке «Авторизация» есть раскрывающийся список, в котором можно установить тип «Токен на предъявителя», после чего токен может перейти в поле токена.
Процесс очень похож на Insomnia.
Пользователи cURL могут сделать то же самое, передав параметр -H "Authorization: Bearer <token>"
, где <token>
— это токен авторизации, полученный из ответа на вход или регистрацию.
Как и в случае с cURL, если разработчики планируют использовать API с помощью axios или подобной библиотеки, они могут добавить заголовок 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 [ // ]; } }
Чтобы использовать это новое уведомление, нам нужно переопределить метод sendPasswordResetNotification
, который User
наследует от класса Authenticatable
. Все, что нам нужно сделать, это добавить это в app/User.php
:
public function sendPasswordResetNotification($token) { $this->notify(new \App\Notifications\MailResetPasswordNotification($token)); }
При правильно функционирующей настройке почты уведомления должны работать на этом этапе.
Осталось только управление доступом пользователей.
Учебное пособие по Laravel Passport, шаг 5: создание промежуточного программного обеспечения для контроля доступа
Прежде чем мы создадим промежуточное программное обеспечение для управления доступом, нам нужно будет обновить таблицу user
, чтобы иметь столбец с именем type
, который будет использоваться для определения уровня пользователя: тип 0 — обычный пользователь, тип 1 — администратор, а тип 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
. Как только это будет сделано, мы должны отредактировать нашу функцию register
в файле ApiAuthController.php
, добавив ее непосредственно перед строкой с $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); } }
Нам также нужно отредактировать функцию handle
в app/Http/Middleware/SuperAdminAuth.php
:
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
Нам нужно изменить index
функцию ArticleController
и зарегистрировать маршрут. (В реальных проектах мы бы использовали PHPUnit и сделали бы это как часть автоматизированного теста. Здесь мы вручную добавляем маршрут для целей тестирования — его можно удалить позже.)
Мы перейдем к контроллеру ArticleController
в app/Http/Controllers/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` = ...
Если это произойдет, разработчики должны убедиться, что выполнили миграцию паспорта и установили ['guards']['api']['driver']
на passport
в config/auth.php
:
'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');
Мы сделали так, что вновь созданному пользователю автоматически присваивается тип 0, как мы можем видеть по маршруту api/user
.
Из-за этого мы должны получить сообщение об ошибке при попытке доступа к конечной точке articles
в качестве такого пользователя.
В целях тестирования давайте изменим пользователя в базе данных, чтобы он имел type
1. После повторной проверки этого изменения через маршрут api/user
мы готовы снова попытаться получить GET
/articles/
.
Это работает отлично.
Разработчики, которые создают более сложные приложения, должны учитывать, что надлежащее управление доступом не будет таким простым. В этом случае другие сторонние приложения или шлюзы и политики Laravel могут использоваться для реализации настраиваемого контроля доступа пользователей. Во второй части этой серии мы рассмотрим более надежные и гибкие решения для контроля доступа.
Аутентификация Laravel API: что мы узнали
В этом руководстве по Laravel Passport мы обсудили:
- Создание фиктивного контроллера и модели, чтобы было что использовать при тестировании нашего примера Laravel Passport.
- Создание промежуточного программного обеспечения, необходимого для бесперебойной работы нашего API, обращение к CORS и принудительное выполнение API, чтобы он всегда возвращал ответы JSON.
- Настройка базовой аутентификации Laravel API: регистрация, вход и выход.
- Настройка функции «сброса пароля» на основе настроек Laravel по умолчанию.
- Создание промежуточного программного обеспечения управления доступом для добавления уровней разрешений пользователей к различным маршрутам.
Это важные навыки для всех, кто работает в сфере услуг разработки Laravel. Читатели найдут конечный результат в этом репозитории GitHub, и теперь они должны быть готовы реализовать аутентификацию с помощью Laravel. Мы с нетерпением ждем комментариев ниже.