A free and open-source book on ZF3 for beginners

Translation into this language is not yet finished. You can help this project by translating the chapters and contributing your changes.

16.8. Filtro de acceso

La última cosa que se implementa en el modulo de usuarios es el filtro de acceso. El filtro de acceso se usa para restringir el acceso a determinadas páginas permitiendo el acceso solo a usuarios autorizados.

El filtro de acceso trabaja de la siguiente manera:

El filtro de acceso esta diseñado para funcionar en uno de estos dos modos: restrictive (por defecto) y permissive. En el modo restrictive el filtro prohíbe el acceso no autorizado a cualquier página que no esta listada en la llave access_filter.

La llave de configuración access_filter se encuentra dentro del archivo module.config.php y será usada por el filtro de acceso. La llave contiene una lista de controladores y nombres de acciones, para cada acción se permitirá o ver la página a cualquiera o ver la página solo a los usuarios autorizados. Un ejemplo de la estructura de la llave se muestra abajo:

// The 'access_filter' key is used by the User module to restrict or permit
// access to certain controller actions for unauthenticated visitors.
'access_filter' => [
    'options' => [
        // The access filter can work in 'restrictive' (recommended) or 'permissive'
        // mode. In restrictive mode all controller actions must be explicitly listed
        // under the 'access_filter' config key, and access is denied to any not listed
        // action for not logged in users. In permissive mode, if an action is not listed
        // under the 'access_filter' key, access to it is permitted to anyone (even for
        // not logged in users. Restrictive mode is more secure and recommended to use.
        'mode' => 'restrictive'
    ],
    'controllers' => [
        Controller\IndexController::class => [
            // Allow anyone to visit "index" and "about" actions
            ['actions' => ['index', 'about'], 'allow' => '*'],
            // Allow authorized users to visit "settings" action
            ['actions' => ['settings'], 'allow' => '@']
        ],
    ]
],

Dentro de la llave access_filter tenemos dos subllaves:

La implementación del filtro de acceso es muy simple. Este no puede, por ejemplo, permitir el acceso basado en nombres o por roles de usuarios. Sin embargo, podemos fácilmente modificar y extenderla como deseemos. Si planeas introducir un control de acceso basado en roles (RBAC), revisa la documentación para el componente de Zend Framework Zend\Permissions\Rbac.

16.8.1. Agregar el listener event dispatch

Para implementar el filtro de control de acceso, usaremos un listener event. Podemos familiarizados con los listener event revisando el capítulo Crear un nuevo modulo.

Prestaremos antención especialmente al evento Dispatch. El evento Dispatch es lanzado después del evento Route, cuando el controllador y la acción son determinados. Para implementar el listener modificamos el archivo Module.php del modulo User de la siguiente manera:

<?php
namespace User;

use Zend\Mvc\MvcEvent;
use Zend\Mvc\Controller\AbstractActionController;
use User\Controller\AuthController;
use User\Service\AuthManager;

class Module
{
    /**
     * This method returns the path to module.config.php file.
     */
    public function getConfig()
    {
        return include __DIR__ . '/../config/module.config.php';
    }

    /**
     * This method is called once the MVC bootstrapping is complete and allows
     * to register event listeners.
     */
    public function onBootstrap(MvcEvent $event)
    {
        // Get event manager.
        $eventManager = $event->getApplication()->getEventManager();
        $sharedEventManager = $eventManager->getSharedManager();
        // Register the event listener method.
        $sharedEventManager->attach(AbstractActionController::class,
                MvcEvent::EVENT_DISPATCH, [$this, 'onDispatch'], 100);
    }

    /**
     * Event listener method for the 'Dispatch' event. We listen to the Dispatch
     * event to call the access filter. The access filter allows to determine if
     * the current visitor is allowed to see the page or not. If he/she
     * is not authorized and is not allowed to see the page, we redirect the user
     * to the login page.
     */
    public function onDispatch(MvcEvent $event)
    {
        // Get controller and action to which the HTTP request was dispatched.
        $controller = $event->getTarget();
        $controllerName = $event->getRouteMatch()->getParam('controller', null);
        $actionName = $event->getRouteMatch()->getParam('action', null);

        // Convert dash-style action name to camel-case.
        $actionName = str_replace('-', '', lcfirst(ucwords($actionName, '-')));

        // Get the instance of AuthManager service.
        $authManager = $event->getApplication()->getServiceManager()->get(AuthManager::class);

        // Execute the access filter on every controller except AuthController
        // (to avoid infinite redirect).
        if ($controllerName!=AuthController::class &&
            !$authManager->filterAccess($controllerName, $actionName)) {

            // Remember the URL of the page the user tried to access. We will
            // redirect the user to that URL after successful login.
            $uri = $event->getApplication()->getRequest()->getUri();
            // Make the URL relative (remove scheme, user info, host name and port)
            // to avoid redirecting to other domain by a malicious user.
            $uri->setScheme(null)
                ->setHost(null)
                ->setPort(null)
                ->setUserInfo(null);
            $redirectUrl = $uri->toString();

            // Redirect the user to the "Login" page.
            return $controller->redirect()->toRoute('login', [],
                    ['query'=>['redirectUrl'=>$redirectUrl]]);
        }
    }
}

16.8.2. Implementar el algoritmo de control de acceso

El event listener onDispatch() llama al método filterAccess() del servicio AuthManager para determinar si la página puede ser vista o no. Mostramos abajo el código del método filterAccess():

/**
 * This is a simple access control filter. It allows vistors to visit certain pages only,
 * the rest requiring the user to be authenticated.
 *
 * This method uses the 'access_filter' key in the config file and determines
 * whenther the current visitor is allowed to access the given controller action
 * or not. It returns true if allowed; otherwise false.
 */
public function filterAccess($controllerName, $actionName)
{
    // Determine mode - 'restrictive' (default) or 'permissive'. In restrictive
    // mode all controller actions must be explicitly listed under the 'access_filter'
    // config key, and access is denied to any not listed action for unauthenticated users.
    // In permissive mode, if an action is not listed under the 'access_filter' key,
    // access to it is permitted to anyone (even for not logged in users.
    // Restrictive mode is more secure and recommended to use.
    $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; // Anyone is allowed to see the page.
                else if ($allow=='@' && $this->authService->hasIdentity()) {
                    return true; // Only authenticated user is allowed to see the page.
                } else {
                    return false; // Access denied.
                }
            }
        }
    }

    // In restrictive mode, we forbid access for unauthorized users to any
    // action not listed under 'access_filter' key (for security reasons).
    if ($mode=='restrictive' && !$this->authService->hasIdentity())
        return false;

    // Permit access to this page.
    return true;
}

16.8.3. Probar el filtro de acceso

Para probar el filtro de acceso, intentamos visitar la página "http://localhost/users" o "http://localhost/settings" sin haber iniciado sesión. El filtro de acceso redireccionará a la página de Login. Sin embargo, podemos visitar sin problemas la página "http://localhost/about", esta está abierta para cualquiera.


Top