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:
Cuando alguien intenta acceder a una página web, revisamos la llave access_filter
de la configuración
y determinamos si se permite el acceso a la página a cualquiera o solo a usuarios autorizados.
Si la página esta disponible para cualquiera se permite al visitante ver la página.
Si la página puede ser accedida solo por usuarios autorizados, revisamos si el usuario esta autorizado o no.
Si el usuario no esta autorizado se redirecciona a la página de login y se piden las credenciales.
Una vez que el usuario inicia sesión se le redirecciona a la página original.
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:
options
puede ser usado para definir el modo en que los filtros funcionan ("restrictive" o "permissive").controllers
lista los controladores y sus acciones especificando el tipo de acceso para cada acción. El
carácter asterisco (*) significa que cualquiera es capaz de acceder a la página web. El carácter arroba (@) significa
que solo los usuarios autorizados son capaces de acceder a la página.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
.
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]]);
}
}
}
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;
}
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.