A free and open-source book on ZF3 for beginners


16.8. Фильтрация доступа

Последнее, что мы реализуем в модуле User - это фильтр доступа. Он будет использоваться для запрета не вошедшим на сайт пользователям доступа к определенным страницам.

Алгоритм работы фильтра доступа описан ниже:

Фильтр доступа предназначен для работы в двух режимах: ограничительном (по умолчанию) и разрешающем. В ограничительном режиме фильтр запрещает не вошедшим пользователям доступ ко всем страницам, которые не указаны под ключом 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 находятся два подключа:

Реализация фильтра доступа очень проста. Он не может, например, разрешать доступ в зависимости от логина или роли пользователя. Однако, вы легко можете изменять и расширять фильтр как пожелаете. Если вы планируете ввести управление доступом на основе ролей (Role Based Access Control - RBAC), обратитесь к главе Контроль доступа на основе ролей.

16.8.1. Добавление обработчика события Dispatch

Для реализации фильтрации доступа мы используем обработчик событий. Вы могли ознакомиться с обработкой событий в главе Создание нового модуля

В частности, мы будем обрабатывать событие 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]]);
        }
    }
}

16.8.2. Реализация алгоритма фильтрации доступа

Обработчик событий 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;
}

16.8.3. Тестирование фильтра доступа

Для проверки работы фильтра доступа попробуйте открыть страницу "http://localhost/users" или "http://localhost/settings", когда вы не авторизованы. Фильтр доступа перенаправит вас на страницу Login. Однако, вы без проблем можете перейти на страницу "http://localhost/about" - она открыта для всех.


Top