Pełne uwierzytelnianie użytkownika i kontrola dostępu — samouczek dotyczący paszportu Laravel, Pt. 1
Opublikowany: 2022-03-11Podczas tworzenia aplikacji internetowej dobrym pomysłem jest podzielenie jej na dwie warstwy. Interfejs API warstwy środkowej wchodzi w interakcję z bazą danych, a warstwa internetowa zwykle składa się z frontonu SPA lub MPA. W ten sposób aplikacja internetowa jest luźniej powiązana, co na dłuższą metę ułatwia zarządzanie i debugowanie.
Po utworzeniu interfejsu API konfiguracja uwierzytelniania i stanu w bezstanowym kontekście interfejsu API może wydawać się nieco problematyczna.
W tym artykule przyjrzymy się, jak zaimplementować pełne uwierzytelnianie użytkownika i prostą formę kontroli dostępu w API przy użyciu Laravel i Passport. Powinieneś mieć doświadczenie w pracy z Laravelem, ponieważ nie jest to samouczek wprowadzający.
Wymagania instalacyjne:
- PHP 7+, MySQL i Apache (programiści, którzy chcą zainstalować wszystkie trzy na raz, mogą używać XAMPP.)
- Kompozytor
- Laravel 7
- Paszport Laravela. Ponieważ interfejsy API są generalnie bezstanowe i nie używają sesji, zazwyczaj używamy tokenów do utrzymywania stanu między żądaniami. Laravel używa biblioteki Passport do implementacji pełnego serwera OAuth2, którego możemy użyć do uwierzytelniania w naszym API.
- Listonosz, cURL lub Insomnia do testowania interfejsu API — to zależy od osobistych preferencji
- Edytor tekstu do wyboru
- Pomocnicy Laravela (dla Laravela 6.0 i nowszych) — po zainstalowaniu Laravela i Passporta po prostu uruchom:
composer require laravel/helpers
Po zainstalowaniu powyższego jesteśmy gotowi do rozpoczęcia. Pamiętaj, aby skonfigurować połączenie z bazą danych, edytując plik .env
.
Samouczek dotyczący paszportu Laravel, krok 1: Dodaj kontroler i model dla fikcyjnych żądań
Najpierw stworzymy kontroler i model dla fikcyjnych żądań. Model nie będzie zbyt przydatny w tym samouczku, służy jedynie do zobrazowania danych, którymi kontroler ma manipulować.
Przed utworzeniem modelu i kontrolera musimy utworzyć migrację. W terminalu — lub w oknie cmd.exe
, jeśli używasz systemu Windows — uruchom:
php artisan make:migration create_articles_table --create=articles
Teraz przejdź do folderu database/migrations
i otwórz plik o nazwie podobnej do xxxx_xx_xx_xxxxxx_create_articles_table.php
.
W funkcji up
klasy napiszemy to:
Schema::create('articles', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->string('body'); $table->integer('user_id'); $table->timestamps(); });
Następnie utworzymy model Article
. Aby to zrobić, uruchom:
php artisan make:model Article
Następnie tworzymy kontroler ArticleController
, uruchamiając:
php artisan make:controller ArticleController --resource
Następnie edytujemy plik app/Providers/AppServiceProvider.php
i importujemy klasę Illuminate\Support\Facades\Schema
, dodając:
use Illuminate\Support\Facades\Schema
…na dole importów na górze pliku.
Następnie w funkcji boot
napiszemy:
Schema::defaultStringLength(191);
Po tym wszystkim możemy uruchomić:
php artisan migrate
…aby zastosować migrację, którą stworzyliśmy powyżej.
Samouczek dotyczący paszportu Laravel, krok 2: Utwórz niezbędne elementy oprogramowania pośredniego
Tutaj dodamy elementy oprogramowania pośredniego, które będą niezbędne do działania API.
Odpowiedzi JSON
Pierwszym potrzebnym elementem jest oprogramowanie pośredniczące ForceJsonResponse
, które automatycznie konwertuje wszystkie odpowiedzi do formatu JSON.
Aby to zrobić, uruchom:
php artisan make:middleware ForceJsonResponse
A to jest funkcja obsługi tego oprogramowania pośredniego, w App/Http/Middleware/ForceJsonReponse.php
:
public function handle($request, Closure $next) { $request->headers->set('Accept', 'application/json'); return $next($request); }
Następnie dodamy oprogramowanie pośredniczące do naszego pliku app/Http/Kernel.php
w tablicy $routeMiddleware
:
'json.response' => \App\Http\Middleware\ForceJsonResponse::class,
Następnie dodamy go również do tablicy $middleware
w tym samym pliku:
\App\Http\Middleware\ForceJsonResponse::class,
Zapewniłoby to, że oprogramowanie pośredniczące ForceJsonResponse
jest uruchamiane przy każdym żądaniu.
CORS (Udostępnianie zasobów między źródłami)
Aby umożliwić użytkownikom naszego Laravel REST API dostęp z innego źródła, musimy skonfigurować CORS. W tym celu utworzymy oprogramowanie pośredniczące o nazwie Cors
.
W terminalu lub wierszu poleceń cd
do katalogu głównego projektu i uruchom:
php artisan make:middleware Cors
Następnie w app/Http/Middleware/Cors.php
dodaj następujący kod:
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'); }
Aby załadować to oprogramowanie pośredniczące, musimy dodać wiersz do tablicy $routeMiddleware
w app/Http/Kernel.php
:
'cors' => \App\Http\Middleware\Cors::class,
Ponadto będziemy musieli dodać go do tablicy $middleware
, tak jak to zrobiliśmy w przypadku poprzedniego oprogramowania pośredniego:
\App\Http\Middleware\Cors::class,
Po wykonaniu tej czynności dołączymy tę grupę tras do routes/api.php
:
Route::group(['middleware' => ['cors', 'json.response']], function () { // ... });
Wszystkie nasze trasy API trafią do tej funkcji, jak zobaczymy poniżej.
Samouczek Laravel Passport, Krok 3: Utwórz kontrolery uwierzytelniania użytkowników dla API
Teraz chcemy stworzyć kontroler uwierzytelniania z funkcjami login
i register
.
Najpierw pobiegniemy:
php artisan make:controller Auth/ApiAuthController
Teraz zaimportujemy niektóre klasy do pliku app/Http/Controllers/Auth/ApiAuthController.php
. Klasy te będą wykorzystywane przy tworzeniu funkcji login
i register
. Zaimportujemy klasy dodając:
use App\User; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str;
…na górę kontrolera.
Teraz, aby dodać uwierzytelnianie Laravel API dla naszych użytkowników, stworzymy funkcje login
, logout
i register
(signup) w tym samym pliku.
Funkcja register
będzie wyglądać tak:
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); }
Funkcja login
wygląda tak:
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); } }
I wreszcie funkcja logout
:
public function logout (Request $request) { $token = $request->user()->token(); $token->revoke(); $response = ['message' => 'You have been successfully logged out!']; return response($response, 200); }
Następnie musimy dodać funkcje login
, register
i logout
do naszych tras, czyli w obrębie grupy tras już w 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'); // ... });
Na koniec musimy dodać cechę HasApiToken
do modelu User
. Przejdź do app/User
i upewnij się, że masz:
use HasApiTokens, Notifiable;
…na szczycie swojej klasy.
Co mamy do tej pory…
Jeśli uruchomimy serwer aplikacji — tj. uruchomimy php artisan serve
— a następnie spróbujemy wysłać żądanie GET
na trasę /api/user
, powinniśmy otrzymać komunikat:
{ "message": "Unauthenticated." }
Dzieje się tak, ponieważ nie jesteśmy uwierzytelnieni, aby uzyskać dostęp do tej trasy. Aby niektóre wybrane przez Ciebie trasy były chronione, możemy je dodać do routes/api.php
zaraz za wierszami Route::post
:
Route::middleware('auth:api')->group(function () { // our routes to be protected will go in here });
Zanim przejdziemy dalej, dodamy ścieżkę wylogowania do oprogramowania pośredniczącego auth:api
, ponieważ Laravel używa tokena do wylogowania użytkownika — tokena, do którego nie można uzyskać dostępu spoza oprogramowania pośredniczącego auth:api
. Nasze trasy publiczne wyglądają tak:
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'); // ... });
Z drugiej strony nasze chronione trasy wyglądają tak:
Route::middleware('auth:api')->group(function () { // our routes to be protected will go in here Route::post('/logout', 'Auth\ApiAuthController@logout')->name('logout.api'); });
Teraz przejdziemy do ArticleController
, który utworzyliśmy w app/Http/Controllers/ArticleController.php
i usuniemy metody create
i edit
w tej klasie. Następnie dodamy następujący fragment kodu, nieco zmieniony, do każdej pozostałej funkcji:
$response = ['message' => '<function name> function']; return response($response, 200);
W razie potrzeby wypełnimy <function name>
. Na przykład funkcja update
będzie miała to jako swoje ciało:
$response = ['message' => 'update function']; return response($response, 200);
Ręczny test uwierzytelniania Laravel: tworzenie użytkownika
Aby zarejestrować użytkownika, wyślemy żądanie POST
do /api/register
z następującymi parametrami: name
, email
(który musi być unikalny), password
i password_confirmation
.
Gdy użytkownik zostanie utworzony, API zwróci token, który wykorzystamy w kolejnych żądaniach jako środek do uwierzytelniania.
Aby się zalogować, wyślemy żądanie POST
do /api/login
. Jeśli nasze dane są poprawne, w ten sposób otrzymamy również token z naszego Laravel login API.
Token autoryzacji, który otrzymujemy z tego żądania, możemy użyć, gdy chcemy uzyskać dostęp do chronionej trasy. W aplikacji Postman zakładka „Autoryzacja” zawiera listę rozwijaną, w której typ można ustawić na „Token na okaziciela”, po czym token może przejść do pola tokena.
Proces jest podobny w przypadku bezsenności.
Użytkownicy cURL mogą zrobić odpowiednik, przekazując parametr -H "Authorization: Bearer <token>"
, gdzie <token>
jest tokenem autoryzacji podanym w odpowiedzi logowania lub rejestracji.
Podobnie jak w przypadku cURL, jeśli programiści planują korzystać z interfejsu API za pomocą axios lub biblioteki tego rodzaju, mogą dodać nagłówek Authorization
z wartością Bearer <token>
.
Samouczek dotyczący paszportu Laravel, krok 4: Utwórz funkcję resetowania hasła
Teraz, gdy uwierzytelnianie podstawowe zostało zakończone, nadszedł czas na skonfigurowanie funkcji resetowania hasła.
Aby to zrobić, możemy wybrać utworzenie katalogu kontrolera api_auth
, utworzenie nowych kontrolerów niestandardowych i zaimplementowanie funkcji; lub możemy edytować kontrolery auth, które możemy wygenerować za pomocą Laravela. W tym przypadku edytujemy kontrolery auth, ponieważ cała aplikacja jest API.

Najpierw wygenerujemy kontrolery auth, uruchamiając:
composer require laravel/ui php artisan ui vue --auth
Zmodyfikujemy klasę w app/Http/Controllers/Auth/ForgotPasswordController.php
, dodając te dwie metody:
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); }
Następnie musimy skonfigurować kontroler, który faktycznie resetuje hasło, więc przejdziemy do app/Http/Controllers/Auth/ResetPasswordController.php
i zastąpimy domyślne funkcje, takie jak:
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); }
Musimy również zaimportować niektóre klasy w kontrolerze dodając:
use Illuminate\Auth\Events\PasswordReset; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash;
…na górę kontrolera.
Będziemy również chcieli zmodyfikować, które powiadomienie e-mail jest używane, ponieważ powiadomienie e-mail dostarczane z Laravel nie używa tokenów API do autoryzacji. Możemy utworzyć nowy w app/Notifications
, uruchamiając to polecenie:
php artisan make:notification MailResetPasswordNotification
Musimy edytować plik app/Notifications/MailResetPasswordNotification.php
, aby wyglądał tak:
<?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 [ // ]; } }
Aby skorzystać z tego nowego powiadomienia, musimy nadpisać metodę sendPasswordResetNotification
, którą User
dziedziczy z klasy Authenticatable
. Wszystko, co musimy zrobić, to dodać to do app/User.php
:
public function sendPasswordResetNotification($token) { $this->notify(new \App\Notifications\MailResetPasswordNotification($token)); }
Przy prawidłowo działającej konfiguracji poczty powiadomienia powinny działać w tym momencie.
Teraz pozostaje tylko kontrola dostępu użytkowników.
Samouczek dotyczący paszportu Laravel, krok 5: Utwórz oprogramowanie pośredniczące kontroli dostępu
Zanim utworzymy oprogramowanie pośredniczące do kontroli dostępu, będziemy musieli zaktualizować tabelę user
, aby zawierała kolumnę o nazwie type
, która będzie używana do określenia poziomu użytkownika: typ 0 to zwykły użytkownik, typ 1 to administrator, a typ 2 to superadministrator.
Aby zaktualizować tabelę user
, musimy utworzyć migrację, uruchamiając to:
php artisan make:migration update_users_table_to_include_type --table=users
W nowo utworzonym pliku formularza database/migrations/[timestamp]_update_users_table.php
, musimy zaktualizować funkcje up
i down
, aby odpowiednio dodać i usunąć kolumnę 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'); }); }
Następnie uruchomimy php artisan migrate
. Gdy to zrobimy, musimy edytować naszą funkcję register
w pliku ApiAuthController.php
, dodając to tuż przed linią z $user = User::create($request->toArray());
:
$request['type'] = $request['type'] ? $request['type'] : 0;
Ponadto musimy dodać tę linię do tablicy $validator
:
'type' => 'integer',
Pierwsza z tych dwóch edycji spowoduje, że wszyscy zarejestrowani użytkownicy będą domyślnie „normalnymi użytkownikami”, tj. jeśli nie zostanie wprowadzony żaden typ użytkownika.
Samo oprogramowanie pośredniczące kontroli dostępu
Teraz jesteśmy w stanie stworzyć dwa elementy oprogramowania pośredniczącego do wykorzystania do kontroli dostępu: jeden dla administratorów i jeden dla superadministratorów.
Więc pobiegniemy:
php artisan make:middleware AdminAuth php artisan make:middleware SuperAdminAuth
Najpierw przejdziemy do app/Http/Middleware/AdminAuth.php
i zaimportujemy Illuminate\Support\Facades\Auth
, a następnie edytujemy funkcję handle
w następujący sposób:
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); } }
Musimy również edytować funkcję handle
w 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); } }
Powinieneś również zaimportować klasę Auth
na górze obu plików, dodając:
use Illuminate\Support\Facades\Auth;
…do dna znalezionych tam importów.
Aby korzystać z naszego nowego oprogramowania pośredniczącego, odwołamy się do obu klas w jądrze — tj. w app/Http/Kernel.php
— dodając następujące wiersze do tablicy $routeMiddleware
:
'api.admin' => \App\Http\Middleware\AdminAuth::class, 'api.superAdmin' => \App\Http\Middleware\SuperAdminAuth::class,
Jeśli programiści chcą używać oprogramowania pośredniczącego w danej trasie, wystarczy dodać je do funkcji trasy w następujący sposób:
Route::post('route','Controller@method')->middleware('<middleware-name-here>');
<middleware-name-here>
w tym przypadku może być odpowiednio api.admin
, api.superAdmin
itp.
To wszystko, co jest potrzebne do stworzenia naszego oprogramowania pośredniczącego.
Kładąc wszystko razem
Aby sprawdzić, czy nasze uwierzytelnianie i kontrola dostępu działają, należy wykonać kilka dodatkowych kroków.
Testowanie uwierzytelniania i kontroli dostępu Laravel: krok 1
Musimy zmodyfikować funkcję index
ArticleController
i zarejestrować trasę. (W rzeczywistych projektach używalibyśmy PHPUnit i zrobilibyśmy to w ramach zautomatyzowanego testu. Tutaj ręcznie dodajemy trasę do celów testowych — można ją później usunąć.)
Przejdziemy do kontrolera ArticleController
w app/Http/Controllers/ArticleController
i zmodyfikujemy funkcję index
, aby wyglądała tak:
public function index() { $response = ['message' => 'article index']; return response($response, 200); }
Następnie zarejestrujemy funkcję w trasie, przechodząc do pliku routes/api.php
i dołączając to:
Route::middleware('auth:api')->group(function () { Route::get('/articles', 'ArticleController@index')->name('articles'); });
Testowanie uwierzytelniania i kontroli dostępu Laravel: krok 2
Teraz możemy spróbować uzyskać dostęp do trasy bez tokena uwierzytelniającego. Powinniśmy otrzymać błąd uwierzytelniania.
Testowanie uwierzytelniania i kontroli dostępu Laravel: krok 3
Możemy również spróbować uzyskać dostęp do tej samej trasy za pomocą tokena autoryzacyjnego (tego, który otrzymaliśmy podczas rejestracji lub zalogowania się we wcześniejszej części tego artykułu).
Czasami może to spowodować błąd podobny do tego:
Unknown column 'api_token' in 'where clause' (SQL: select * from `users` where `api_token` = ...
Jeśli tak się stanie, programiści powinni upewnić się, że przeprowadzili migrację Passport i ustawić ['guards']['api']['driver']
na passport
w config/auth.php
:
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ],
Następnie pamięć podręczna konfiguracji również wymaga aktualizacji.
Po naprawieniu powinniśmy mieć dostęp do trasy.
Testowanie uwierzytelniania i kontroli dostępu Laravel: krok 4
Czas przetestować kontrolę dostępu. Dodajmy ->middleware('api.admin')
do trasy artykułów, więc wygląda to tak:
Route::get('/articles', 'ArticleController@index')->middleware('api.admin')->name('articles');
Zrobiliśmy to tak, że nowo utworzonemu użytkownikowi automatycznie przypisywany jest typ 0, co możemy zobaczyć za pośrednictwem trasy api/user
.
Z tego powodu powinniśmy otrzymać błąd podczas próby uzyskania dostępu do punktu końcowego articles
jako taki użytkownik.
Na potrzeby testów zmodyfikujmy użytkownika w bazie danych, aby miał type
1. Po ponownym zweryfikowaniu tej zmiany za pośrednictwem trasy api/user
, jesteśmy gotowi, aby ponownie spróbować GET
trasę /articles/
.
Działa doskonale.
Deweloperzy tworzący bardziej złożone aplikacje powinni pamiętać, że odpowiednie kontrole dostępu nie będą takie proste. W takim przypadku inne aplikacje innych firm lub bramki i polityki Laravela mogą zostać użyte do wdrożenia niestandardowej kontroli dostępu użytkownika. W drugiej części tej serii przyjrzymy się bardziej niezawodnym i elastycznym rozwiązaniom kontroli dostępu.
Uwierzytelnianie Laravel API: czego się nauczyliśmy
W tym samouczku dotyczącym paszportu Laravel omówiliśmy:
- Stworzenie fikcyjnego kontrolera i modelu, aby mieć coś do wykorzystania podczas testowania naszego przykładu Laravel Passport.
- Stworzenie oprogramowania pośredniego niezbędnego do sprawnego działania naszego API, adresowanie CORS i zmuszanie API do zwracania odpowiedzi JSON.
- Konfigurowanie podstawowego uwierzytelniania Laravel API: rejestracja, logowanie i wylogowanie.
- Konfigurowanie funkcji „resetowania hasła” w oparciu o domyślne ustawienia Laravela.
- Tworzenie oprogramowania pośredniczącego kontroli dostępu w celu dodania poziomów uprawnień użytkownika do różnych tras.
Są to niezbędne umiejętności dla każdego, kto pracuje w dziedzinie usług programistycznych Laravel. Czytelnicy znajdą efekt końcowy w tym repozytorium GitHub i powinni być teraz dobrze przygotowani do wdrożenia uwierzytelniania za pomocą Laravela. Czekamy na komentarze poniżej.