Последнее, что мы реализуем в модуле User
- это фильтр доступа. Он будет использоваться
для запрета не вошедшим на сайт пользователям доступа к определенным страницам.
Алгоритм работы фильтра доступа описан ниже:
Когда кто-то пытается обратиться к веб-странице, мы проверяем, во-первых, ключ конфигурации приложения
access_filter
, а во-вторых, то, доступна ли эта страница всем или только вошедшим на сайт пользователям.
Если страница доступна всем, позволяем посетителю сайта перейти на страницу.
Если страница доступна только вошедшим на сайт пользователям, проверяем, вошел ли пользователь в систему.
Если пользователь не вошел на сайт, перенаправляем его на страницу Login и предлагаем войти в аккаунт.
После того как пользователь войдет на сайт, перенаправляем его обратно на исходную страницу.
Фильтр доступа предназначен для работы в двух режимах: ограничительном (по умолчанию) и разрешающем. В ограничительном режиме
фильтр запрещает не вошедшим пользователям доступ ко всем страницам, которые не указаны под ключом access_filter
.
Ключ конфигурации access_filter
, находящийся в файле module.config.php, будет использоваться фильтром доступа. Он будет
содержать список имен контроллеров и действий и для каждого действия либо разрешать доступ к странице всем, либо разрешать
доступ к странице только для вошедших пользователей. Пример структуры этого ключа представлен ниже:
// Ключ 'access_filter' используется модулем User, чтобы разрешить или запретить доступ к
// определенным действиям контроллера для не вошедших на сайт пользователей.
'access_filter' => [
'options' => [
// Фильтр доступа может работать в 'ограничительном' (рекомендуется) или 'разрешающем'
// режиме. В ограничительном режиме все действия контроллера должны быть явно перечислены
// под ключом конфигурации 'access_filter', а доступ к любому не перечисленному действию
// для неавторизованных пользователей запрещен. В разрешающем режиме, даже если действие не
// указано под ключом 'access_filter', доступ к нему разрешен для всех (даже для
// неавторизованных пользователей. Рекомендуется использовать более безопасный ограничительный режим.
'mode' => 'restrictive'
],
'controllers' => [
Controller\IndexController::class => [
// Позволяем всем обращаться к действиям "index" и "about".
['actions' => ['index', 'about'], 'allow' => '*'],
// Позволяем вошедшим на сайт пользователям обращаться к действию "settings".
['actions' => ['settings'], 'allow' => '@']
],
]
],
Под ключом access_filter
находятся два подключа:
options
, который можно использовать для определения режима, в котором функционирует фильтр ("ограничительный" или "разрешающий"). controllers
содержит список контроллеров и их действий с указанием типа доступа для каждого действия. Звездочка (*) означает, что
доступ к веб-странице есть у всех. Символ "коммерческое at" (@) означает, что доступ к странице есть только у авторизованных пользователей.Реализация фильтра доступа очень проста. Он не может, например, разрешать доступ в зависимости от логина или роли пользователя. Однако, вы легко можете изменять и расширять фильтр как пожелаете. Если вы планируете ввести управление доступом на основе ролей (Role Based Access Control - RBAC), обратитесь к главе Контроль доступа на основе ролей.
Для реализации фильтрации доступа мы используем обработчик событий. Вы могли ознакомиться с обработкой событий в главе Создание нового модуля
В частности, мы будем обрабатывать событие Dispatch. Оно вызывается после события Route, когда контроллер и действие уже определены. Чтобы реализовать обработчик, мы изменим файл Module.php модуля User следующим образом:
<?php
namespace User;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\Controller\AbstractActionController;
use User\Controller\AuthController;
use User\Service\AuthManager;
class Module
{
/**
* Этот метод возвращает путь к файлу module.config.php file.
*/
public function getConfig()
{
return include __DIR__ . '/../config/module.config.php';
}
/**
* Этот метод вызывается после завершения самозагрузки MVC и позволяет
* регистрировать обработчики событий.
*/
public function onBootstrap(MvcEvent $event)
{
// Получаем менеджер событий.
$eventManager = $event->getApplication()->getEventManager();
$sharedEventManager = $eventManager->getSharedManager();
// Регистрируем метод-обработчик.
$sharedEventManager->attach(AbstractActionController::class,
MvcEvent::EVENT_DISPATCH, [$this, 'onDispatch'], 100);
}
/**
* Метод-обработчик для события 'Dispatch'. Мы обрабатываем событие Dispatch
* для вызова фильтра доступа. Фильтр доступа позволяет определить,
* может ли пользователь просматривать страницу. Если пользователь не
* авторизован, и у него нет прав для просмотра, мы перенаправляем его
* на страницу входа на сайт.
*/
public function onDispatch(MvcEvent $event)
{
// Получаем контроллер и действие, которому был отправлен HTTP-запрос.
$controller = $event->getTarget();
$controllerName = $event->getRouteMatch()->getParam('controller', null);
$actionName = $event->getRouteMatch()->getParam('action', null);
// Конвертируем имя действия с пунктирами в имя в верблюжьем регистре.
$actionName = str_replace('-', '', lcfirst(ucwords($actionName, '-')));
// Получаем экземпляр сервиса AuthManager.
$authManager = $event->getApplication()->getServiceManager()->get(AuthManager::class);
// Выполняем фильтр доступа для каждого контроллера кроме AuthController
// (чтобы избежать бесконечного перенаправления).
if ($controllerName!=AuthController::class &&
!$authManager->filterAccess($controllerName, $actionName)) {
// Запоминаем URL страницы, к которой пытался обратиться пользователь. Мы перенаправим пользователя
// на этот URL после успешной авторизации.
$uri = $event->getApplication()->getRequest()->getUri();
// Делаем URL относительным (убираем схему, информацию о пользователе, имя хоста и порт),
// чтобы избежать перенаправления на другой домен недоброжелателем.
$uri->setScheme(null)
->setHost(null)
->setPort(null)
->setUserInfo(null);
$redirectUrl = $uri->toString();
// Перенаправляем пользователя на страницу "Login".
return $controller->redirect()->toRoute('login', [],
['query'=>['redirectUrl'=>$redirectUrl]]);
}
}
}
Обработчик событий onDispatch()
вызывает метод filterAccess()
сервиса AuthManager
, чтобы определить,
можно ли пользователю просматривать страницу. Код метода filterAccess()
представлен ниже:
/**
* Это простой фильтр контроля доступа. Он может ограничить доступ к определенным страницам
* для неавторизованных пользователей.
*
* Этот метод использует ключ 'access_filter' в файле конфигурации и определяет,
* разрешен ли текущему посетителю доступ к заданному действию контроллера. Если
* разрешен, он возвращает true, иначе - false.
*/
public function filterAccess($controllerName, $actionName)
{
// Определяем режим - 'ограничительный' (по умолчанию) или 'разрешающий'. В ограничительном
// режиме все действия контроллеров должны быть явно перечислены под ключом конфигурации 'access_filter',
// и для неавторизованных пользователей доступ будет запрещен к любому не указанному в этом списке действию.
// В разрешающем режиме, если действие не указано под ключом 'access_filter' доступ к нему все равно
// разрешен для всех (даже для неавторизованных пользователей). Рекомендуется использовать более безопасный
// ограничительный режим.
$mode = isset($this->config['options']['mode'])?$this->config['options']['mode']:'restrictive';
if ($mode!='restrictive' && $mode!='permissive')
throw new \Exception('Invalid access filter mode (expected either restrictive or permissive mode');
if (isset($this->config['controllers'][$controllerName])) {
$items = $this->config['controllers'][$controllerName];
foreach ($items as $item) {
$actionList = $item['actions'];
$allow = $item['allow'];
if (is_array($actionList) && in_array($actionName, $actionList) ||
$actionList=='*') {
if ($allow=='*')
return true; // Все могут просматривать страницу.
else if ($allow=='@' && $this->authService->hasIdentity()) {
return true; // Только аутентифицированный пользователь может просматривать страницу.
} else {
return false; // В доступе отказано.
}
}
}
}
// В ограничительном режиме мы запрещаем неавторизованным пользователям доступ к любому действию,
// не перечисленному под ключом 'access_filter' (из соображений безопасности).
if ($mode=='restrictive' && !$this->authService->hasIdentity())
return false;
// Разрешаем доступ к этой странице.
return true;
}
Для проверки работы фильтра доступа попробуйте открыть страницу "http://localhost/users" или "http://localhost/settings", когда вы не авторизованы. Фильтр доступа перенаправит вас на страницу Login. Однако, вы без проблем можете перейти на страницу "http://localhost/about" - она открыта для всех.