Аутентификация

Введение

{tip} Хотите быстро начать работу? Просто запустите php artisan make:auth и php artisan migrate в свежем приложении Laravel. Затем перейдите в браузере по адресу http://your-app.dev/register или откройте любой другой URL, который назначен вашему приложению. Эти две команды позаботятся о создании модулей для всей вашей системы аутентификации!

В Laravel сделать аутентификацию очень просто. Фактически, почти всё сконфигурировано для вас уже изначально. Конфигурационный файл аутентификации расположен в config/auth.php, который содержит несколько хорошо описанных опций для тонкой настройки поведения служб аутентификации.

По сути средства аутентификации Laravel состоят из "гвардов" и "провайдеров". Гварды определяют то, как именно аутентифицируются пользователи, для каждого запроса. Например, Laravel поставляется с гвардом session, который поддерживает состояние аутентифицированности с помощью хранилища сессий и кук.

Провайдеры определяют то, как именно пользователи извлекаются из вашей базы данных. Laravel поставляется с поддержкой извлечения пользователей с помощью Eloquent и конструктора запросов БД. Но при необходимости вы можете определить дополнительные провайдеры для своего приложения.

Не переживайте, если сейчас это звучит слишком запутанно! Для большинства приложений никогда не потребуется изменять стандартные настройки аутентификации.

Требования для базы данных

По умолчанию в Laravel есть модель Eloquent App\User в директории app. Эта модель может использоваться с базовым драйвером аутентификации Eloquent. Если ваше приложение не использует Eloquent, вы можете использовать драйвер аутентификации database, который использует конструктор запросов Laravel.

При создании схемы базы данных для модели App\User создайте столбец для паролей с длиной не менее 60 символов. Хорошим выбором будет длина 255 символов.

Кроме того, перед началом работы удостоверьтесь, что ваша таблица users (или эквивалентная) содержит строковый столбец remember_token на 100 символов. Этот столбец будет использоваться для хранения ключей сессий «запомнить меня», обрабатываемых вашим приложением.

Краткое руководство по аутентификации

Laravel поставляется с несколькими контроллерами аутентификации, расположенными в пространстве имён App\Http\Controllers\Auth. RegisterController обрабатывает регистрацию нового пользователя, LoginController - его аутентификацию, ForgotPasswordController обрабатывает отправку по электронной почте ссылок на сброс пароля, а ResetPasswordController содержит логику для сброса паролей. Каждый из этих контроллеров использует типажи для подключения необходимых методов. Для многих приложений вам вообще не придётся изменять эти контроллеры.

Роутинг

Laravel обеспечивает быстрый способ создания заготовок всех необходимых для аутентификации роутов и шаблонов с помощью одной команды:

php artisan make:auth

Эту команду надо использовать на свежем приложении, она установит шаблоны для регистрации и входа, а также роуты для всех конечных точек аутентификации. Также будет сгенерирован HomeController, который обслуживает запросы к панели настроек вашего приложения после входа. Но вы можете изменить или даже удалить это контроллер, если это необходимо для вашего приложения.

Шаблоны

Как было упомянуто в предыдущем разделе, команда php artisan make:auth создаст все необходимые вам шаблоны для аутентификации и поместит их в директорию resources/views/auth.

Команда make:auth также создаст директорию resources/views/layouts, содержащую основной макет для вашего приложения. Все эти макеты используют CSS-фреймворк Bootstrap, но вы можете изменять их как угодно.

Аутентификация

Теперь, когда у вас есть роуты и шаблоны для имеющихся контроллеров аутентификации, вы готовы регистрировать и аутентифицировать новых пользователей своего приложения! Вы можете просто перейти по этим роутам в браузере. Контроллеры аутентификации уже содержат логику (благодаря их типажам) для аутентификации существующих пользователей и сохранения новых пользователей в базе данных.

Изменение пути

Когда пользователь успешно аутентифицируется, он будет перенаправлен на URI /home. Вы можете изменить место для перенаправления после входа, задав свойство redirectTo контроллеров LoginController, RegisterController и ResetPasswordController:

protected $redirectTo = '/';

Если для пути перенаправления требуется собственная логика генерации, можно задать метод redirectTo вместо свойства redirectTo:

protected function redirectTo()
{
    return '/path';
}

{tip} Метод redirectTo имеет приоритет над атрибутом redirectTo.

Настройка имени пользователя

По умолчанию для аутентификации Laravel использует поле email. Если вы хотите это изменить, то можете определить метод username в своем LoginController:

public function username()
{
    return 'username';
}

Настройка гварда

Вы также можете изменить "гварда", который используется для аутентификации и регистрации пользователей. Для начала задайте метод guard в LoginController, RegisterController и ResetPasswordController. Метод должен возвращать экземпляр гварда:

use Illuminate\Support\Facades\Auth;

protected function guard()
{
    return Auth::guard('guard-name');
}

Настройка валидации / хранения

Чтобы изменить требуемые поля для формы регистрации нового пользователя, или для изменения способа добавления новых записей в вашу базу данных, вы можете изменить класс RegisterController. Этот класс отвечает за проверку ввода и создание новых пользователей в вашем приложении.

Метод validator класса RegisterController содержит правила проверки ввода данных для новых пользователей приложения.

Метод create класса RegisterController отвечает за создание новых записей App\User в вашей базе данных с помощью Eloquent ORM. Вы можете изменить каждый из этих методов, как пожелаете.

Получение аутентифицированного пользователя

Вы можете обращаться к аутентифицированному пользователю через фасад Auth:

use Illuminate\Support\Facades\Auth;

// получить текущего залогиненного юзера
$user = Auth::user();

// получить id текущего залогиненного юзера
$id = Auth::id();

Или, когда пользователь аутентифицирован, вы можете обращаться к нему через экземпляр Illuminate\Http\Request. Не забывайте, что указание типов классов приводит к их автоматическому внедрению в методы вашего контроллера:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ProfileController extends Controller
{
    /**
     * Обновление профиля пользователя.
     *
     * @param  Request  $request
     * @return Response
     */
    public function update(Request $request)
    {
        // $request->user() возвращает экземпляр аутентифицированного пользователя...
    }
}

Определение, аутентифицирован ли пользователь

Чтобы определить, что пользователь уже вошёл в ваше приложение, вы можете использовать метод check фасада Auth, который вернёт true, если пользователь аутентифицирован:

use Illuminate\Support\Facades\Auth;

if (Auth::check()) {
    // Пользователь вошёл в систему...
}

{tip} Хотя и возможно определить аутентифицирован ли пользователь, используя метод check, обычно вы будете использовать посредников для проверки был ли пользователь аутентифицирован ранее, позволяя этому пользователю получать доступ к определенным роутам / контроллерам. Для получения дополнительной информации смотрите документацию о защите роутов.

Защита роутов

Посредник Route можно использовать, чтобы давать доступ к определённому роуту только аутентифицированным пользователям. Laravel поставляется с посредником auth, который определён в Illuminate\Auth\Middleware\Authenticate. Так как этот посредник уже зарегистрирован в вашем HTTP ядре, всё, что вам надо сделать — это присоединить его к определению роута:

Route::get('profile', function () {
    // Только аутентифицированные пользователи могут зайти...
})->middleware('auth');

Конечно, если вы используете контроллеры, вы можете вызвать метод middleware из конструктора контроллера, вместо присоединения его напрямую к определению роута:

public function __construct()
{
    $this->middleware('auth');
}

Указание гварда

Во время прикрепления посредника auth к роуту, вы можете также указать, какой гвард должен быть использован для выполнения аутентификации. Указанный гвард должен соответствовать одному из ключей в массиве guards вашего конфига auth.php:

public function __construct()
{
    $this->middleware('auth:api');
}

Троттлинг аутентификации (ограничение числа неудачных попыток входа)

Если вы используете встроенный в Laravel класс LoginController, трейт Illuminate\Foundation\Auth\ThrottlesLogins уже будет включён в ваш контроллер. По умолчанию пользователь не сможет войти в приложение в течение одной минуты, если он несколько раз указал неправильные данные для входа. Ограничение происходит отдельно для имени пользователя/адреса e-mail и его IP-адреса.

Ручная аутентификация пользователей

Конечно, совсем не обязательно использовать контроллеры аутентификации, включенные в Laravel. Если вы захотите убрать эти контроллеры, вам нужно будет напрямую управлять аутентификацией пользователей, используя классы аутентификации Laravel. Не волнуйтесь, они не кусаются!

Мы будем работать со службами аутентификации Laravel через фасад Auth, поэтому нам надо не забыть импортировать фасад Auth в начале класса. Далее давайте посмотрим на метод attempt:

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    /**
     * Обработка попытки аутентификации.
     *
     * @return Response
     */
    public function authenticate()
    {
        if (Auth::attempt(['email' => $email, 'password' => $password])) {
            // Аутентификация успешна...
            return redirect()->intended('dashboard');
        }
    }
}

Метод attempt принимает массив пар ключ/значение в качестве первого аргумента. Значения массива будут использованы для поиска пользователя в таблице базы данных. Так, в приведённом выше примере пользователь будет получен по значению столбца email. Если пользователь будет найден, хешированный пароль, сохранённый в базе данных, будет сравниваться с хешированным значением password , переданным в метод через массив. Если два хешированных пароля совпадут, то для пользователя будет запущена новая аутентифицированная сессия.

Метод attempt вернет true, если аутентификация прошла успешно. В противном случае будет возвращён false.

Метод intended "редиректора" перенаправит пользователя к тому URL, к которому он обращался до того, как был перехвачен фильтром аутентификации. В этот метод можно передать запасной URI, на случай недоступности требуемого пути.

Указание дополнительных условий

При необходимости вы можете добавить дополнительные условия к запросу аутентификации, помимо адреса e-mail и пароля. Например, можно проверить отметку "активности" пользователя:

if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
    // Пользователь активен, не приостановлен и существует.
}

{note} В этих примерах email не является обязательным вариантом, он приведён только для примера. Вы можете использовать какой угодно столбец, соответствующий "username" в вашей базе данных.

Обращение к конкретным экземплярам гварда

С помощью метода guard фасада Auth вы можете указать, какой экземпляр гварда необходимо использовать. Это позволяет управлять аутентификацией для отдельных частей вашего приложения, используя полностью отдельные модели для аутентификации или таблицы пользователей.

Передаваемое в метод guard имя гварда должно соответствовать одному из защитников, настроенных в конфиге auth.php:

if (Auth::guard('admin')->attempt($credentials)) {
    //
}

Завершение сессии

Для завершения сессии пользователя можно использовать метод logout фасада Auth. Он очистит информацию об аутентификации в сессии пользователя:

Auth::logout();

Запоминание пользователей

Если вы хотите обеспечить функциональность "запомнить меня" в вашем приложении, вы можете передать логическое значение как второй параметр методу attempt, который сохранит пользователя аутентифицированным на неопределённое время, или пока он вручную не выйдет из системы. Конечно, ваша таблица users должна содержать строковый столбец remember_token, который будет использоваться для хранения токенов "запомнить меня".

if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
    // Пользователь запомнен...
}

{tip} Если вы используете встроенный в Laravel LoginController, логика "запоминания" пользователей уже реализована трейтами, используемыми контроллером.

Если вы "запоминаете" пользователей, то можете использовать метод viaRemember , чтобы определить, аутентифицировался ли пользователь, используя cookie "запомнить меня":

if (Auth::viaRemember()) {
    //
}

Другие методы аутентификации

Аутентификация экземпляра пользователя

Если вам необходимо "залогинить" в приложение существующий экземпляр пользователя, вызовите метод login с экземпляром пользователя. Данный объект должен быть реализацией контракта Illuminate\Contracts\Auth\Authenticatable. Конечно, встроенная в Laravel модель App\User реализует этот интерфейс:

Auth::login($user);

// Войти и "запомнить" данного пользователя...
Auth::login($user, true);

Конечно, вы можете указать, какой экземпляр гварда надо использовать:

Auth::guard('admin')->login($user);

Аутентификация пользователя по ID

Для входа пользователя в приложение по его ID, используйте метод loginUsingId. Этот метод просто принимает первичный ключ пользователя, которого необходимо аутентифицировать:

Auth::loginUsingId(1);

// Войти и "запомнить" данного пользователя...
Auth::loginUsingId(1, true);

Однократная аутентификация пользователя

Вы также можете использовать метод once для пользовательского входа в систему для одного запроса. Сеансы и cookies не будут использоваться, что может быть полезно при создании API без сохранения состояний:

if (Auth::once($credentials)) {
    //
}

HTTP-аутентификация

HTTP-аутентификация — простой и быстрый способ аутентификации пользователей вашего приложения без создания дополнительной страницы входа. Для начала прикрепите посредника auth.basic к своему роуту. Посредник auth.basic встроен в Laravel, поэтому вам не надо определять его:

Route::get('profile', function () {
    // Войти могут только аутентифицированные пользователи...
})->middleware('auth.basic');

Когда посредник прикреплён к роуту, вы автоматически получите запрос данных для входа при обращении к роуту через браузер. По умолчанию посредник auth.basic будет использовать столбец email из записи пользователя в качестве "username".

Замечание по FastCGI

Если вы используете PHP FastCGI, то простая HTTP-аутентификация изначально может работать неправильно. Надо добавить следующие строки к вашему файлу .htaccess:

RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

HTTP-аутентификация без сохранения состояния

Вы также можете использовать HTTP-аутентификацию, не задавая пользовательскую cookie для сессии, что особенно полезно для API-аутентификации. Чтобы это сделать, определите посредника, который вызывает метод onceBasic. Если этот метод ничего не возвращает, запрос может быть передан дальше в приложение:

<?php

namespace Illuminate\Auth\Middleware;

use Illuminate\Support\Facades\Auth;

class AuthenticateOnceWithBasicAuth
{
    /**
     * Обработка входящего запроса.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, $next)
    {
        return Auth::onceBasic() ?: $next($request);
    }

}

Затем зарегистрируйте посредника роута и прикрепите его к роуту:

Route::get('api/user', function () {
    // Войти могут только аутентифицированные пользователи...
})->middleware('auth.basic.once');

Добавление собственных гвардов

Вы можете определить собственные гварды аутентификации, используя метод extend фасада Auth. Вы должны поместить этот вызов provider внутри сервис-провайдера. Так как в состав Laravel уже входит AuthServiceProvider, можно поместить данный код в провайдер:

<?php

namespace App\Providers;

use App\Services\Auth\JwtGuard;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * Выполнение пост-регистрационной загрузки служб.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Auth::extend('jwt', function ($app, $name, array $config) {
            // Вернуть экземпляр Illuminate\Contracts\Auth\Guard...

            return new JwtGuard(Auth::createUserProvider($config['provider']));
        });
    }
}

Как видите, в этом примере переданная в метод extend анонимная функция должна вернуть реализацию Illuminate\Contracts\Auth\Guard. Этот интерфейс содержит несколько методов, которые вам надо реализовать, для определения собственного гварда. Когда вы определили своего гварда, вы можете использовать его в настройке guards своего конфига auth.php:

'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],

Добавление собственных провайдеров пользователей

Если вы не используете традиционную реляционную базу данных для хранения ваших пользователей, вам необходимо добавить в Laravel свой собственный провайдер аутентификации пользователей. Мы используем метод provider фасада Auth для определения своего провайдера:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Auth;
use App\Extensions\RiakUserProvider;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * Выполнение пост-регистрационной загрузки служб.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Auth::provider('riak', function ($app, array $config) {
            // Вернуть экземпляр Illuminate\Contracts\Auth\UserProvider...

            return new RiakUserProvider($app->make('riak.connection'));
        });
    }
}

После регистрации провайдера методом provider, вы можете переключиться на новый провайдер в файле настроек auth.php. Сначала определите провайдера provider, который использует ваш новый драйвер:

'providers' => [
    'users' => [
        'driver' => 'riak',
    ],
],

Затем вы можете использовать этот провайдер в вашей настройке guards:

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
],

Контракт User Provider

Реализации Illuminate\Contracts\Auth\UserProvider отвечают только за извлечение реализаций Illuminate\Contracts\Auth\Authenticatable из постоянных систем хранения, таких как MySQL, Riak, и т.п. Эти два интерфейса позволяют механизмам аутентификации Laravel продолжать функционировать независимо от того, как хранятся данные пользователей и какой тип класса использован для их представления.

Давайте взглянем на контракт Illuminate\Contracts\Auth\UserProvider:

<?php

namespace Illuminate\Contracts\Auth;

interface UserProvider {

    public function retrieveById($identifier);
    public function retrieveByToken($identifier, $token);
    public function updateRememberToken(Authenticatable $user, $token);
    public function retrieveByCredentials(array $credentials);
    public function validateCredentials(Authenticatable $user, array $credentials);

}

Функция retrieveById обычно принимает ключ, отображающий пользователя, такой как автоинкрементный ID из базы данных MySQL. Реализация Authenticatable, соответствующая этому ID, должна быть получена и возвращена этим методом.

Функция retrieveByToken принимает пользователя по его уникальному $identifier и ключу $token "запомнить меня", хранящемуся в поле remember_token. Как и предыдущий метод, он должен возвращать реализацию Authenticatable.

Метод updateRememberToken обновляет поле remember_token пользователя $user значением нового $token. Новый ключ может быть как свежим ключом, назначенным при успешной попытке входа "запомнить меня", так и нулевым при выходе пользователя.

Метод retrieveByCredentials принимает массив авторизационных данных, переданных в метод Auth::attempt при попытке входа в приложение. Затем метод должен "запросить" у основного постоянного хранилища того пользователя, который соответствует этим авторизационным данным. Обычно этот метод выполняет запрос с условием "where" для $credentials['username']. Затем метод должен вернуть реализацию Authenticatable. Этот метод не должен пытаться проверить пароль или аутентифицировать пользователя.

Метод validateCredentials должен сравнить данного $user с $credentials для аутентификации пользователя. Например, этот метод может сравнить строку $user->getAuthPassword() с Hash::check для сравнения значения $credentials['password']. Этот метод должен возвращать true или false, указывая верен ли пароль.

Контракт Authenticatable

Теперь, когда мы изучили каждый метод в UserProvider, давайте посмотрим на контракт Authenticatable. Помните, провайдер должен вернуть реализацию этого интерфейса из методов retrieveById и retrieveByCredentials:

<?php

namespace Illuminate\Contracts\Auth;

interface Authenticatable {

    public function getAuthIdentifierName();
    public function getAuthIdentifier();
    public function getAuthPassword();
    public function getRememberToken();
    public function setRememberToken($value);
    public function getRememberTokenName();

}

Этот интерфейс довольно прост. Метод getAuthIdentifierName должен возвращать имя поля "первичного ключа» пользователя", а метод getAuthIdentifier - "первичный ключ" пользователя. При использовании MySQL это будет автоинкрементный первичный ключ. Метод getAuthPassword должен возвращать хешированный пароль пользователя. Этот интерфейс позволяет системе аутентификации работать с классом User, независимо от используемой ORM и уровня абстракции хранилища. По умолчанию Laravel содержит в директории app класс User, который реализует этот интерфейс. Вы можете подсмотреть в нём пример реализации.

События

Laravel генерирует различные события в процессе аутентификации. Вы можете прикрепить слушателей к этим событиям в вашем EventServiceProvider:

/**
 * Сопоставления слушателя событий для вашего приложения.
 *
 * @var array
 */
protected $listen = [
    'Illuminate\Auth\Events\Registered' => [
        'App\Listeners\LogRegisteredUser',
    ],

    'Illuminate\Auth\Events\Attempting' => [
        'App\Listeners\LogAuthenticationAttempt',
    ],

    'Illuminate\Auth\Events\Authenticated' => [
        'App\Listeners\LogAuthenticated',
    ],

    'Illuminate\Auth\Events\Login' => [
        'App\Listeners\LogSuccessfulLogin',
    ],

    'Illuminate\Auth\Events\Failed' => [
        'App\Listeners\LogFailedLogin',
    ],

    'Illuminate\Auth\Events\Logout' => [
        'App\Listeners\LogSuccessfulLogout',
    ],

    'Illuminate\Auth\Events\Lockout' => [
        'App\Listeners\LogLockout',
    ],
];