На прошлой неделе мы создали функционал, позволяющий людям регистрироваться и авторизоваться в Cribb’е. Практически все веб-приложения требуют авторизации в том или ином виде. Авторизация — это не только ограничение доступа к некоторым страницам, но также ограничение доступа к широкому спектру возможностей пользователя.
Для того, чтобы написать хорошее веб-приложение, важно абстрагировать эту функциональность. Но не следует разбрасывать логику ограничения доступа к определенным страницам или методам по всему проекту, поскольку поддержка такого кода станет настоящим кошмаром.
Для примера предположим, что критерий авторизации в вашем приложении должен измениться, а логика определения прав доступа разбросана по сотням файлов. Вы непременно забудете обновить некоторые части вашего кода, что приведет к уязвимости перед потенциальным взломщиком.
В Laravel 4 эта проблема решается за счет выноса этой логики в фильтры. Фильтры позволяют ограничить доступ к определенным маршрутам. Становится тривиально просто ограничить доступ только администраторам к административным разделам вашего сайта, или позволить только зарегистрированным пользователям отправлять новый контент для сайта.
В этой статье мы исследуем всю функциональность и применимость фильтров Laravel, и увидим, как они позволяют сделать наше приложение надежным и безопасным.
Что такое «фильтр»?
По существу, фильтр — это кусок кода, который должен отрабатывать либо до, либо после того, как Laravel направит запрос по определенному маршруту.
К примеру, если вам нужно ограничить доступ к определенному маршруту только зарегистрированным пользователям, вы могли бы присоединить фильтр авторизации, который будет определять, авторизован ли текущий пользователь, или нет.
Когда Laravel попытается направить пользователя к запрашиваемой странице, он автоматически выполнит логику фильтра перед тем, как обработать маршрут.
Если фильтр вернет свой ответ, Laravel обработает его вместо изначально запрашиваемого. Таким образом, чтобы ограничить доступ к некоторым маршрутам только для зарегистрированных пользователей, вам надо будет проверить, не является ли текущий пользователь гостем. Если это гость, то надо будет вернуть редирект на страницу авторизации.
Таким образом, как вы можете видеть, фильтр — это просто логика, которая может быть выполнена перед или после маршрутизации запроса.
C Laraverl’ом поставляется несколько полезных фильтров. Если вы откроете app/filters.php, то увидите список предустановленных фильтров.
Входящие и исходящие фильтры
Первые два фильтра, которые вы увидите, это входящий (before) и исходящий (after) фильтры всего приложения:
App::before(function($request)
{
//
});
App::after(function($request, $response)
{
//
});
Эти два фильтра позволяют вам зарегистрировать код, который должен выполниться перед или после каждого запроса. Эти два события являются частью жизненного цикла любого запроса в Laravel. То есть, к примеру, если вам нужно логировать все запросы, или опрашивать различные службы, вы можете добиться того, что этот код будет выполняться автоматически при каждом запросе.
Анатомия фильтра
Чтобы понять, как работает фильтр, давайте рассмотрим один из наиболее важных фильтров — фильтр авторизации:
Route::filter('auth', function()
{
if (Auth::guest()) return Redirect::guest('login');
});
Метод PHPfilter()
класса PHPRoute
принимает два аргумента.
Первый аргумент — это имя фильтра, в данном случае auth. Нам необходимо присваивать имя фильтру чтобы впоследствии присоединить фильтр к маршруту.
Второй аргумент — это функция-замыкание. Замыкание — это место где вы определяете логику конкретного фильтра.
В приведенном выше фильтре, Laravel проверяет, не является ли текущий пользователь гостем. Если это гость, фильтр возвращает редирект на страницу авторизации. Если же фильтр возвращает ответ, Laravel вернет его вместо изначально запрашиваемого.
Присоединение фильтра к маршруту
Как только у вас появится фильтр, его нужно присоединить к маршруту чтобы от него был эффект.
Чтобы присоединить фильтр, просто передайте его в качестве параметра во втором аргументе определения маршрута:
Route::get('user/account', array('before' => 'auth',
'uses' => 'UserController@account',
'as' => 'user.account'
));
В примере выше я присоединяю фильтр auth, чтобы удостовериться, что только зарегистрированные пользователи получат доступ к странице учетной записи.
Чтобы присоединить несколько фильтров, просто разделите их вертикальной чертой:
Route::get('user/premium', array('before' => 'auth|premium',
'uses' => 'UserController@premium',
'as' => 'user.premium'
));
В этом примере я проверяю, что пользователь не только авторизован, но также является премиум-подписчиком.
При использовании нескольких фильтров важно отметить, что если первый фильтр вернет результат, все последующие будут отменены. В данной ситуации это имеет смысл, так как нет смысла проверять, является ли пользователь премиум-подписчиком, если он вообще не авторизован. Но тем не менее, это важно помнить при построении маршрутов и фильтров.
Шаблонные фильтры
Присоединение фильтров к каждому вашему маршруту очень быстро может превратиться в мешанину. К счастью, в Laravel есть пара способов предотвратить лишнее повторение кода.
Первый способ — это воспользоваться шаблонным фильтром:
Route::filter('admin', function()
{
// Это админ?
});
Route::when('admin/*', 'admin');
В этом примере ко всем URL’ам, начинающимся с admin/ будет автоматически применен фильтр admin.
Вы также можете ограничить фильтр HTTP-методами:
Route::when('post/*', 'auth', array('post', 'put', 'delete'));
В этом примере только авторизованные пользователи смогут создавать, редактировать или удалять сообщения из приложения.
Групповые фильтры
Использовать шаблонные фильтры идеально для ситуации, когда вы хотите присоединить фильтр к весьма специфичному набору маршрутов, как в примере выше. Однако довольно часто вашим маршрутам трудно будет подобрать определенный шаблон и таким образом вам понадобится несколько шаблонов, чтобы покрыть все маршруты.
Лучшим решением будет использовать групповые фильтры:
Route::group(array('before' => 'auth'), function()
{
Route::get('user/account', 'UserController@account');
Route::get('user/settings', 'UserController@settings');
Route::get('post/create', 'PostController@create');
Route::post('post/store', 'PostController@store');
// ...
});
В этом примере ко всем маршрутам группы будете применен фильтр.
Классы фильтров
Во всех вышеприведенных примерах мы использовали замыкания, содержащие логику фильтра. Laravel также позволяет создавать специальный класс для ваших фильтров.
Зачем вам может это понадобиться? Ну, если у вас много особенно сложных фильтров, возможно будет иметь смысл вынести их из файла filters.php, чтобы предотвратить мешанину в нем. Это сильно облегчит организацию и поддержку ваших фильтров.
Классы фильтров также используют IoC-контейнер Laravel. Это означает, что они могут автоматически использовать внедрение зависимостей (dependency injection), таким образом вы можете с легкостью проверить, что они работают правильно.
Примером может послужить следующий код:
class ApiFilter {
public function filter()
{
if (!$this->valid($access_token)
return Response::json(array(
'error' => 'Ваша проверочная строка не верна'
), 403);
}
}
Вы можете зарегистрировать это класс-фильтр таким образом:
Route::filter('api.auth', 'ApiFilter');
Заключение
Фильтры позволяют вам с легкостью выносить сложную логику проверки доступа к маршруту в краткие и легкие в использовании куски кода. Это позволяет определить логику всего один раз, а использовать ее во многих маршрутах.
99% всех проектов так или и иначе будут использовать фильтры. Хоть я в основном рассмотрел примеры авторизации в этой статье, фильтры могут быть применены к широкому спектру случаев, например, где вы хотите ограничить доступ к определенному маршруту на основе определенной логики, или хотите, чтобы определенный код выполнился перед или после обработки определенных запросов.
Итого, вместо того, чтобы разбрасывать этот тип логики по всему приложению, вынесите ее в фильтр, который будет определен однажды и может быть использован повторно. Это не только сделает ваш код чище, но он также будет более поддерживаемым и менее подвержен ошибкам в будущем, если вам придется изменить логику.