Створення кастомного guard аутентифікації в Laravel

20

Від автора: у цій статті ми розглянемо систему аутентифікації в фреймворку Laravel. Мета статті – створити кастомный Guard аутентифікації, розширивши базову систему аутентифікації. Laravel надає дуже надійну систему аутентифікації в ядрі, що спрощує реалізацію стандартної аутентифікації. На справі, для встановлення системи аутентифікації необхідно запустити пару вивчених команд.

Сама система спроектована таким чином, що ви можете розширити її і вставити у неї свої кастомні адаптери аутентифікації. Це ми детально обговоримо в цій статті. Перш ніж ми з головою зануримося в реалізацію кастомного Guard аутентифікації, давайте обговоримо базові елементи, завдяки яким працює аутентифікація Laravel – Guard і провайдери.

Елементи ядра: Guard і провайдери

Система аутентифікації Laravel складається з двох елементів – Guard і провайдери.

Guard

Уявіть Guard, як спосіб доставки логіки, що використовується для ідентифікації пройшли перевірку користувачів. В ядрі Laravel є різні Guard – сесія і токен. Guard сесії обслуговує стан користувача по всіх запитах через кукі, а Guard токен перевіряє справжність користувача через токен в кожному запиті.

Guard визначає логіку аутентифікації, і необов’язково, щоб він постійно отримував валідні дані авторизації з back end. Можна зробити Guard, який просто перевіряє наявність певних даних у заголовках запиту і на їх основі проводить аутентифікацію.

Нижче в статті ми створимо Guard перевірки певних JSON параметрів у заголовках запиту, який буде отримувати валідного користувача з MongoDB back end.

Провайдери

Якщо Guard визначає логіку аутентифікації, то провайдер аутентифікації отримує користувача з сховища back end. Якщо Guard вимагає, щоб користувач перевірявся через back end сховище, то реалізація отримання користувача переходить в провайдер аутентифікації.

Laravel йде з двома стандартними провайдерами аутентифікації — Database і Eloquent. Провайдер аутентифікації Database працює з простим витягуванням даних авторизації користувача з сховища back end, а Eloquent дає абстрактний шар.

У нашому прикладі ми реалізуємо провайдер аутентифікації MongoDB, який буде отримувати дані авторизації користувача через MongoDB back end.

Це було базове уявлення Guard і провайдерів в системі аутентифікації Laravel. Починаючи з наступного розділу, ми будемо розробляти кастомні Guard аутентифікації і провайдер!

Швидка настройка файлів

Давайте швидко пробіжимося по файлів, які нам необхідно створити в цій статті.

config/auth.php: файл налаштувань аутентифікації, в який ми додамо точку входу в кастомный Guard.

config/mongo.php: файл налаштувань MongoDB.

app/Services/Contracts/NosqlServiceInterface.php: інтерфейс, який реалізує наш кастомный клас Mongo database.

app/Database/MongoDatabase.php: головний клас бази даних, взаємодіє з MongoDB.

app/Models/Auth/User.php: клас моделі User, реалізує договір Authenticable.

app/Extensions/MongoUserProvider.php: реалізація провайдера аутентифікації.

app/Services/Auth/JsonGuard.php: реалізація Guard драйвера аутентифікації.

app/Providers/AuthServiceProvider.php: файл, за допомогою якого ми будемо додавати прив’язки до сервіс контейнерів.

app/Http/Controllers/MongoController.php: файл демо контролера, з допомогою якого ми будемо тестувати наш кастомный Guard.

Не лякайтеся, що список файлів поки що вам нічого не говорить, ми поступово все розберемо.

Реалізація

У цьому розділі ми створимо необхідні файли. Насамперед нам необхідно інформувати Laravel про кастомном Guard. Скопіюйте наступний код у файл config/auth.php.



‘guards’ => [
‘web’ => [
‘driver’ => ‘session’,
‘provider’ => ‘users’,
],
‘api’ => [
‘driver’ => ‘token’,
‘provider’ => ‘users’,
],
‘custom’ => [
‘driver’ => ‘json’,
‘provider’ => ‘mongo’,
],
],

Як бачите, ми додали кастомный Guard під ключем custom. Далі необхідно додати відповідний провайдер в розділ providers.



‘providers’ => [
‘users’ => [
‘driver’ => ‘eloquent’,
‘model’ => App\User::class,
],
‘mongo’ => [
‘driver’ => ‘mongo’
],
// ‘users’ => [
// ‘driver’ => ‘database’,
// ‘table’ => ‘users’,
// ],
],

Провайдер доданий під ключем mongo. Змінимо стандартний Guard аутентифікації з web на custom.



‘defaults’ => [
‘guard’ => ‘custom’,
‘passwords’ => ‘users’,
],

Звичайно, поки що нічого не працює, так як ми не створили необхідні файли. Цим ми і займемося в наступних розділах.

Створення MongoDB Driver

У цьому розділі ми створимо необхідні файли, які будуть спілкуватися з об’єктом MongoDB. Спочатку створимо файл налаштувань config/mongo.php, в якому будуть зберігатися стандартні налаштування підключення MongoDB.

[
‘host’ => ‘{HOST_IP}’,
‘port’ => ‘{HOST_PORT}’,
‘database’ => ‘{DB_NAME}’
]
];

Дані необхідно замінити на свої. Замість класу, взаємодіючого з MongoDB ми створимо інтерфейс.

Плюс інтерфейсу в тому, що він дає абстрактність, яку розробник повинен дотримуватися при його реалізації. Також нашу реалізацію MongoDB можна легко замінити на іншу NoSQL реалізацію без необхідності.

Створіть файл інтерфейсу app/Services/Contracts/NosqlServiceInterface.php і додайте в нього наступний код.

Простий інтерфейс, що визначає CRUD методи, які повинен визначити клас, який реалізує цей інтерфейс. Тепер створимо клас app/Database/MongoDatabase.php.

connection = new MongoClient( «mongodb://{$host}:{$port}» );
$this->database = $this->connection->{$database};
}
/**
* @see \App\Services\Contracts\NosqlServiceInterface::find()
*/
public function find($collection, Array $criteria)
{
return $this->database->{$collection}->findOne($criteria);
}
public function create($collection, Array $document) {}
public function update($collection, $id, Array $document) {}
public function delete($collection, $id) {}
}

Припускаю, що ви вже встановили MongoDB і відповідне розширення MongoDB PHP.

Метод __construct створює об’єкт класу MongoClient з необхідними параметрами. Інший важливий метод, потрібний нам – find. Він отримує запис на основі переданих аргументів. Ми реалізували драйвер MongoDB і намагалися зберегти його простоту.

Створення моделі User

Дотримуючись стандартів системи аутентифікації, нам необхідно реалізувати модель User, яка має реалізовувати контракт Illuminate\Contracts\Auth\Authenticatable. Створіть файл app/Models/Auth/User.php з таким кодом.

conn = $conn;
}
/**
* Fetch user by Credentials
*
* @param array $credentials
* @return Illuminate\Contracts\Auth\Authenticatable
*/
public function fetchUserByCredentials(Array $credentials)
{
$arr_user = $this->conn->find(‘users’, [‘username’ => $credentials[‘username’]]);
if (! is_null($arr_user)) {
$this->username = $arr_user[‘username’];
$this->password = $arr_user[‘password’];
}
return $this;
}
/**
* {@inheritDoc}
* @see \Illuminate\Contracts\Auth\Authenticatable::getAuthIdentifierName()
*/
public function getAuthIdentifierName()
{
return «username»;
}
/**
* {@inheritDoc}
* @see \Illuminate\Contracts\Auth\Authenticatable::getAuthIdentifier()
*/
public function getAuthIdentifier()
{
return $this->{$this->getAuthIdentifierName()};
}
/**
* {@inheritDoc}
* @see \Illuminate\Contracts\Auth\Authenticatable::getAuthPassword()
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* {@inheritDoc}
* @see \Illuminate\Contracts\Auth\Authenticatable::getRememberToken()
*/
public function getRememberToken()
{
if (! empty($this->getRememberTokenName())) {
return $this->{$this->getRememberTokenName()};
}
}
/**
* {@inheritDoc}
* @see \Illuminate\Contracts\Auth\Authenticatable::setRememberToken()
*/
public function setRememberToken($value)
{
if (! empty($this->getRememberTokenName())) {
$this->{$this->getRememberTokenName()} = $value;
}
}
/**
* {@inheritDoc}
* @see \Illuminate\Contracts\Auth\Authenticatable::getRememberTokenName()
*/
public function getRememberTokenName()
{
return $this->rememberTokenName;
}
}

Можливо, ви вже помітили, що App\Models\Auth\User реалізує контракт Illuminate\Contracts\Auth\Authenticatable.

Більшість методів, реалізованих у нашому класі, говорять самі за себе. Ми визначили метод fetchUserByCredentials, який витягує користувача з back end. В нашому випадку це буде клас MongoDatabase, саме до нього ми будемо звертатися за необхідною інформацією. Ми створили реалізацію моделі User.

Створення провайдера аутентифікації

Ми вже говорили раніше, що система аутентифікації Laravel складається з двох елементів – Guard і провайдери. У цьому розділі ми створимо провайдер аутентифікації, який буде витягувати користувача з back end. Створіть файл app/Extensions/MongoUserProvider.php як показано нижче.

model = $userModel;
}
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials)) {
return;
}
$user = $this->model->fetchUserByCredentials([‘username’ => $credentials[‘username’]]);
return $user;
}
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials Request credentials
* @return bool
*/
public function validateCredentials(Authenticatable $user, Array $credentials)
{
return ($credentials[‘username’] == $user->getAuthIdentifier() &&
md5($credentials[‘password’]) == $user->getAuthPassword());
}
public function retrieveById($identifier) {}
public function retrieveByToken($identifier, $token) {}
public function updateRememberToken(Authenticatable $user, $token) {}
}

Необхідно, щоб кастомный провайдер реалізовував контракт Illuminate\Contracts\Auth\UserProvider. Файл визначає два важливих методу retrieveByCredentials і validateCredentials.

Метод retrieveByCredentials використовується для отримання даних авторизації користувача через модель User, про яку ми говорили в попередньому розділі. Метод validateCredentials перевіряє користувача по заданому набору даних.

Ми створили кастомный провайдер аутентифікації. У наступному розділі ми перейдемо до створення Guard, який буде взаємодіяти з провайдером аутентифікації MongoUserProvider.

Створення Guard аутентифікації

Як ми вже говорили раніше, Guard в системі аутентифікації Laravel визначає логіку аутентифікації користувача. Ми будемо перевіряти наявність параметра запиту jsondata. У ньому повинна бути JSON рядок з даними авторизації.

У цьому розділі ми створимо Guard, який буде спілкуватися з провайдером аутентифікації, створеному в попередньому розділі. Створіть файл app/Services/Auth/JsonGuard.php з таким кодом.

request = $request;
$this->provider = $provider;
$this->user = NULL;
}
/**
* Determine if the current user is authenticated.
*
* @return bool
*/
public function check()
{
return ! is_null($this->user());
}
/**
* Determine if the current user is a guest.
*
* @return bool
*/
public function guest()
{
return ! $this->check();
}
/**
* Get the currently authenticated user.
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
if (! is_null($this->user)) {
return $this->user;
}
}
/**
* Get the JSON params from the current request
*
* @return string
*/
public function getJsonParams()
{
$jsondata = $this->request->query(‘jsondata’);
return (!empty($jsondata) ? json_decode($jsondata, TRUE) : NULL);
}
/**
* Get the ID for the currently authenticated user.
*
* @return string|null
*/
public function id()
{
if ($user = $this->user()) {
return $this->user()->getAuthIdentifier();
}
}
/**
* Validate a user’s credentials.
*
* @return bool
*/
public function validate(Array $credentials=[])
{
if (empty($credentials[‘username’]) || empty($credentials[‘password’])) {
if (!$credentials=$this->getJsonParams()) {
return false;
}
}
$user = $this->provider->retrieveByCredentials($credentials);
if (! is_null($user) && $this->provider->validateCredentials($user, $credentials)) {
$this->setUser($user);
return true;
} else {
return false;
}
}
/**
* Set the current user.
*
* @param Array $user User info
* @return void
*/
public function setUser(Authenticatable $user)
{
$this->user = $user;
return $this;
}
}

Перш за все, нашого класу необхідно реалізовувати інтерфейс Illuminate\Contracts\Auth\Guard. Тому необхідно визначити всі методи, оголошені в інтерфейсі.

Важливо відзначити той факт, що функція __construct вимагає реалізації Illuminate\Contracts\Auth\UserProvider. Ми передамо об’єкт App\Extensions\MongoUserProvider.

Далі є функція getJsonParams, яка отримує дані авторизації користувача з параметра jsondata запиту. Очікується рядок JSON з даними авторизації користувача, тому ми декодируем JSON дані за допомогою функції json_decode.

У функції валідації перше, що ми перевіряємо – існування аргументу $credentials. Якщо його немає, викликається метод getJsonParams для отримання даних авторизації користувача з параметрів запиту.

Далі ми викликаємо метод retrieveByCredentials провайдера MongoUserProvider, який отримує користувача з бази даних MongoDB з back end. І нарешті, метод validateCredentials провайдера MongoUserProvider валидирует користувача.

Це була реалізація кастомного Guard. У наступному розділі йдеться, як зв’язати всі ці частини разом і сформувати систему аутентифікації.

Збираємо всі разом

Ми розробили всі елементи кастомного Guard аутентифікації, які повинні дати нам нову систему аутентифікації. Однак прямо ось так вона не запрацює. Її необхідно зареєструвати в прив’язці сервіс контейнера Laravel.

Відкрийте файл app/Providers/AuthServiceProvider.php. З його допомогою можна додати прив’язки сервіс контейнера аутентифікації. Якщо в ньому немає кастомних змін, можете просто замінити код на код нижче.

‘App\Policies\ModelPolicy’,
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
$this->app->bind(‘App\Database\MongoDatabase’, function ($app) {
return new MongoDatabase(config(‘mongo.defaults.host’), config(‘mongo.defaults.port’), config(‘mongo.defaults.database’));
});
$this->app->bind(‘App\Models\Auth\User’, function ($app) {
return new User($app->make(‘App\Database\MongoDatabase’));
});
// add custom guard provider
Auth::provider(‘mongo’, function ($app, array $config) {
return new MongoUserProvider($app->make(‘App\Models\Auth\User’));
});
// add custom guard
Auth::extend(‘json’, function ($app, $name, array $config) {
return new JsonGuard(Auth::createUserProvider($config[‘provider’]), $app->make(‘request’));
});
}
public function register()
{
$this->app->bind(
‘App\Services\Contracts\NosqlServiceInterface’,
‘App\Database\MongoDatabase’
);
}
}

Розглянемо метод boot, в якому зберігається велика частина прив’язок провайдера. Для початку створимо прив’язки для App\Database\MongoDatabase і App\Models\Auth\User.

$this->app->bind(‘App\Database\MongoDatabase’, function ($app) {
return new MongoDatabase(config(‘mongo.defaults.host’), config(‘mongo.defaults.port’), config(‘mongo.defaults.database’));
});
$this->app->bind(‘App\Models\Auth\User’, function ($app) {
return new User($app->make(‘App\Database\MongoDatabase’));
});

Настав час вставити наш кастомный Guard в систему аутентифікації Laravel.

З допомогою провайдер методу Auth Facade ми додали наш кастомный провайдер аутентифікації під ключ mongo. Згадуйте, цей ключ відображає налаштування, які ми раніше вносили в файл auth.php.

Auth::provider(‘mongo’, function ($app, array $config) {
return new MongoUserProvider($app->make(‘App\Models\Auth\User’));
});

Точно так само ми вставимо наша реалізацію кастомного Guard за допомогою методу extend Auth façade.

Auth::extend(‘json’, function ($app, $name, array $config) {
return new JsonGuard(Auth::createUserProvider($config[‘provider’]), $app->make(‘request’));
});

Далі йде метод register, з допомогою якого ми прив’язали інтерфейс App\Services\Contracts\NosqlServiceInterface до реалізації App\Database\MongoDatabase.

$this->app->bind(
‘App\Services\Contracts\NosqlServiceInterface’,
‘App\Database\MongoDatabase’
);

Тепер коли знадобиться вирішити залежність App\Services\Contracts\NosqlServiceInterface, Laravel відповість реалізацією адаптера App\Database\MongoDatabase.

Перевага цього підходу в тому, що реалізацію можна легко замінити на іншу. Наприклад, хтось захотів замінити реалізацію App\Database\MongoDatabase на адаптер CouchDB. Для цього йому необхідно лише додати відповідну прив’язку в метод register.

Сервіс провайдер у вашому розпорядженні. Зараз у нас є все необхідне для тестування кастомних реалізації Guard. Залишився розділ, підводить підсумки.

Це працює?

Ви виконали складну роботу по створенню свого першого кастомного Guard аутентифікації. Пора спробувати його в дії. Швидко створимо простий файл контролера app/Http/Controllers/MongoController.php.

validate()) {
// get the current authenticated user
$user = $auth_guard->user();
echo ‘Success!’;
} else {
echo ‘Not authorized to access this page!’;
}
}
}

Докладно розберемо залежність методу login, який вимагає реалізацію Guard Illuminate\Contracts\Auth\Guard. У файлі auth.php за замовчуванням ми поставили Guard custom, тому вставлятися будемо App\Services\Auth\JsonGuard!

Далі ми викликали метод validate класу App\Services\Auth\JsonGuard, який запускає серію викликів методів:

викликається метод retrieveByCredentials класу App\Extensions\MongoUserProvider

метод retrieveByCredentials викликає метод fetchUserByCredentials класу App\Models\Auth\User

метод fetchUserByCredentials викликає метод find класу App\Database\MongoDatabase і отримує дані авторизації користувача

метод find класу App\Database\MongoDatabase повертає відповідь!

Якщо все відпрацює, як очікується, ми повинні отримати аутентифицированного користувача через виклик методу user нашого Guard.

Для доступу до контролеру необхідно додати відповідний рауса у файл routes/web.php.

Route::get(‘/custom/mongo/login’, ‘[email protected]’);

Спробуйте відкрити http://your-laravel-site/custom/mongo/login, не передаючи параметри. Повинно з’явитися повідомлення «not authorized».

Якщо відкрити щось типу http://your-laravel-site/custom/mongo/login?jsondata={«username»:»admin»,»password»:»admin»}, має повернутися успішне повідомлення, якщо користувач є в базі даних.

Зауважте, що це лише демонстрація можливостей кастомного Guard. Такі речі, як логін, необхідно робити з захистом від дурня. Я ж показав лише загальний погляд на процес аутентифікації. Ви самі повинні створити надійну та безпечну систему для свого додатка.

Наша пригода закінчується, незабаром повернуся з нової корисної статті, сподіваюся. Хочете запитати щось по якій-небудь темі, пишіть!

Висновок

Фреймворк Laravel надає надійну систему аутентифікації в ядрі, яку можна розширити кастомних системою. Темою сьогоднішньої статті стала реалізація кастомного Guard і його вставка в процес аутентифікації Laravel.

В процесі ми розробили систему, аутентифицирующую користувача з JSON в запиті та перевіряє ці дані з базою даних MongoDB. Для цього ми створили кастомный Guard і провайдер.

Сподіваюся, цей приклад показав вам загальний погляд на аутентифікацію в Laravel, і тепер ви краще розумієте її внутрішній устрій.