我需要做一个自定义的isGranted
方法(不使用Rbac或acl模块从社区)。我有一个提供功能的服务。但是这段代码:
if (!$this->userService->isGrantedCustom($this->session->offsetGet('cod_lvl'), 'ZF_INV_HOM')) {
throw new 'Exception("you_are_not_allowed", 1);
}
…在我的每个控制器和每个动作中都是重复的。参数的变化取决于权限('ZF_INV_HOM'
, 'ZF_TODO_DELETE'
…)
我认为在控制器被调用之前做这个代码不是一个坏主意,但我不知道什么是最好的解决方案(最好的架构),以及如何将这些参数传递给它(我考虑过控制器上的注释,但如何处理这个?)。
关键是,如果我要修改这段代码我无法想象要修改几百次,对于每个控制器,每个动作我需要在一个地方有这段代码
如果你不想让所有这些代码污染你的模块,你也可以创建一个侦听器类,并只在你的bootstrap方法中附加侦听器:
<?php
namespace Application'Listener;
use Application'Service'UserService;
use Zend'Mvc'Controller'AbstractActionController;
use Zend'Mvc'MvcEvent;
use Zend'EventManager'SharedEventManagerInterface;
use Zend'EventManager'SharedListenerAggregateInterface;
use Zend'Authentication'AuthenticationServiceInterface;
class IsAllowedListener implements SharedListenerAggregateInterface
{
/**
* @var AuthenticationServiceInterface
*/
protected $authService;
/**
* @var UserService
*/
protected $userService;
/**
* @var 'Zend'Stdlib'CallbackHandler[]
*/
protected $sharedListeners = array();
/**
* @param SharedEventManagerInterface $events
*/
public function attachShared(SharedEventManagerInterface $events)
{
$this->sharedListeners[] = $events->attach(AbstractActionController::class, MvcEvent::EVENT_DISPATCH, array($this, 'isAllowed'), 1000);
}
public function __construct(AuthenticationServiceInterface $authService, UserService $userService ){
$this->authService = $authService;
$this->userService = $userService;
}
/**
* @param MvcEvent $event
*/
protected function isAllowed(MvcEvent $event)
{
$authService = $this->getAuthService();
$identity = $authService->getIdentity();
$userService = $this->getUserService();
if($userService->isGrantedCustom()){
// User is granted we can return
return;
}
// Return not allowed response
}
/**
* @return AuthenticationServiceInterface
*/
public function getAuthService()
{
return $this->authService;
}
/**
* @param AuthenticationServiceInterface $authService
*/
public function setAuthService(AuthenticationServiceInterface $authService)
{
$this->authService = $authService;
}
/**
* @return UserService
*/
public function getUserService()
{
return $this->userService;
}
/**
* @param UserService $userService
*/
public function setUserService(AuthenticationServiceInterface $userService)
{
$this->userService = $userService;
}
}
你需要设置一个工厂来注入你的依赖:
<?php
namespace Application'Listener;
use Zend'ServiceManager'FactoryInterface;
use Zend'ServiceManager'ServiceLocatorInterface;
/**
* Factory for creating the IsAllowedListener
*/
class IsAllowedListenerFactory implements FactoryInterface
{
/**
* Create the IsAllowedListener
*
* @param ServiceLocatorInterface $serviceLocator
* @return RenderLinksListener
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$authService = $serviceManager->get('Zend'Authentication'AuthenticationService');
$userService = $serviceLocator->get('Application'Service'UserService');
return new IsAllowedListener($authService, $userService );
}
}
并在config中注册所有这些:
'service_manager' => array(
'factories' => array(
'Application'Listener'IsAllowedListener' => 'Application'Listener'IsAllowedListenerFactory'
)
)
然后在bootstrap中:
public function onBootstrap(EventInterface $event)
{
$application = $event->getTarget();
$serviceManager = $application->getServiceManager();
$eventManager = $application->getEventManager();
$sharedEventManager = $eventManager->getSharedManager();
$isAllowedListener = $serviceManager->get('Application'Listener'IsAllowedListener')
$sharedEventManager->attachAggregate($isAllowedListener);
}
您也可以创建一个特定的类,而不是使用AbstractActionController::class
,这样您将只侦听该类的实例。
比如AbstractIsAllowedActionController::class
之类的
通过将事件监听器附加到SharedEventManager,您可以针对所有控制器并在一个地方进行授权检查。
在这种情况下,目标是Zend'Mvc'Controller'AbstractActionController
,这意味着任何扩展它的控制器都将执行侦听器。此侦听器的高优先级意味着它在目标控制器操作之前执行,从而使您有机会处理未授权的任何请求。
public function onBootstrap(MvcEvent $event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager()->getSharedManager();
$eventManager->attach(
'Zend'Mvc'Controller'AbstractActionController::class, // Identity of the target controller
MvcEvent::EVENT_DISPATCH,
[$this, 'isAllowed'],
1000 // high priority
);
}
在每个控制器中,需要有一些方法可以确定正在访问哪个"资源"。
作为一个例子,它可以实现这个接口
interface ResourceInterface
{
// Return a unique key representing the resource
public function getResourceId();
}
监听器可以是这样的:
public function isAllowed(MvcEvent $event)
{
$serviceManager = $event->getApplication()->getServiceManager();
// We need the 'current' user identity
$authService = $serviceManager->get('Zend'Authentication'AuthenticationService');
$identity = $authService->getIdentity();
// The service that performs the authorization
$userService = $serviceManager->get('MyModule'Service'UserService');
// The target controller is itself a resource (the thing we want to access)
// in this example it returns an resource id so we know what we want to access
// but you could also get this 'id' from the request or config etc
$controller = $event->getTarget();
if ($controller instanceof ResourceInterface) {
$resourceName = $controller->getResourceId();
// Test the authorization, is UserX allowed resource ID Y
if (empty($resourceName) || $userService->isGrantedCustom($identity, $resourceName)) {
// early exit for success
return;
} else {
// Denied; perhaps trigger a new custom event or return a response
}
}
}