zf2 acl 转发到登录表单
zf2 acl forwarding to login form
我想将未经身份验证的用户(来宾)转发到登录表单。
使用重定向时,我需要将来宾用户从他们正在查找的页面重定向到登录页面;然后再将它们重定向回来。
示例:
(访客访问)mySite/controller/action?var1=xxx&var2=yyy
(AclService 重定向)mySite/login
(AuthService 重定向)mySite/controller/action?var1=xxx&var2=yyy
所有这一切只能(我猜)使用会话变量。
我的想法是将用户转发到 mySite/login。当认证成功后,我唯一需要做的就是重定向到当前URL.
这种行为的优点是,如果用户单击浏览器的后退按钮,页面保持不变 (mySite/controller/action?var1=xxx&var2=yyy).
这是我的代码:
在module.php
public function onBootstrap(MvcEvent $e){
$app = $e->getApplication();
$sm = $app->getServiceManager();
$acl = $sm->get('AclService');
$e -> getApplication()-> getEventManager()->getSharedManager()->attach('Zend\Mvc\Controller\AbstractActionController',MvcEvent::EVENT_DISPATCH,array($acl, 'checkAcl'),-10);
}
在我的 AclService 中,checkAcl 函数
[...]
if (!$this->acl ->isAllowed($role, $controller, $action)){
//when my AuthService->hasIdentity() returns false, the the role is guest
if($role=='guest'){
$controllerClass = get_class($e->getTarget());
//this prevents nested forwards
if($controllerClass!='Auth\Controller\Auth2Controller'){
$e->setResult($e->getTarget()->forward()->dispatch('Auth\Controller\Auth2',array('action'=>'index')));
}
}
else{...}
}
然后在我的 AuthService 中,我使用这个函数(在 mysite/login 中调用)重定向经过身份验证的用户
//if the login page has ben forwarded
if($e->getRouteMatch()->getParam('controller')!='Auth\Controller\Auth2'
{
$url=$this->Config['webhost'].$e->getRequest()->getRequestUri();
return $e->getTarget()->redirect()->toUrl($url);
}
//if the request comes directly from a login/register attempt
else{return $e->getTarget()->redirect()->toRoute('user_home');}
你怎么看?有道理吗?
你知道更好的方法吗?
回答我自己的问题:
不,这没有意义。那是因为
Forwarding is a pattern to dispatch a controller when another
controller has already been dispatched
(Forward to another controller/action from module.php)
因此,acl 检查部分无用,因为放置在受保护页面中的任何内容都将在转发之前执行。
这意味着如果我有一个仅为经过身份验证的用户设计的用户页面(控制器)并且我调用了一个函数,如:
$name=$this->getUserName($userId);
我会收到一条错误消息,因为 var $userId 应该在身份验证过程中设置。等等...
无论如何,我找到了一个更好的解决方案,其行为完全不同但结果相同(显然没有上述问题)。
但是,由于我最初的问题非常具体,为了清楚起见,我想更好地解释一下前缀目标:
我有一个 acl 脚本,它在发送之前运行,如果用户未通过身份验证(角色=来宾),它会将用户重定向到登录表单。
当用户通过登录表单发送凭据并通过身份验证时,我想根据他们到达表单的方式执行动态重定向。
动态重定向应遵循以下条件:
当用户通过 www.site.com/login 到达表单时,一旦通过身份验证,他们应该被重定向到 www.site.com/user_home
当用户通过 www.site.com/controller/action/param?var=xxx&var2=yyy[...] 到达表单时,一旦通过身份验证,他们应该被重定向到相同的 url 不丢失任何参数。
一旦用户通过身份验证,他们应该无法访问页面 www.site.com/login(因为没有用)。这意味着寻找此页面的用户应该被重定向到其他地方。
但是我也是从假设开始的 "redirect is bad":
如果不是直接到达,浏览器历史记录中应该不会有任何www.site.com/login的踪迹。
这是因为浏览器的后退按钮可能会搞砸第 2 点(冲突)中所说的任何内容。示例:
- 我到达页面站点。com/user/post/2016?tag=foo 但我没有登录或会话已过期
- 然后我被重定向到 www.site.com/login,在那里我填写并提交了身份验证表格
- 通过身份验证后,我将再次重定向到站点。com/user/post/2016?tag=foo
- 但是,如果我通过浏览器的按钮返回,那么我的请求的来源是 www.site。com/login 并且,如第 1 点和第 3 点中所定义,
我将被重定向到 www.site.com/user_home 失去 return 到 site.com/user/post/2016?tag=foo 的能力(如果不是通过历史)。 (这就是我尝试使用 forward 插件的原因)
我发现实现此目标的解决方案是使用重定向插件以及使用 MVC 的 Routemach 的自己动手的转发过程。
我按照 samsonasik 的教程开始编码:https://samsonasik.wordpress.com/2013/05/29/zend-framework-2-working-with-authenticationservice-and-db-session-save-handler/
与samsonasik的代码的主要区别在于我添加了一个名为authService的服务,它单独处理与身份验证相关的功能。
这只是为了使代码更多 readable/reusable 因为我需要一些自定义逻辑(与我的应用程序设计相关)在身份验证时执行。
这里是我结构的主要部分:
Application
Auth
->service
->authService
->aclService
Guest
->Controller
->guestHomeController
->loginAction
->singUpAction
->...
->...
User
->Controller
->userHomeController
->...
public(角色=来宾)应该可以访问来宾模块中的任何内容,但已经通过身份验证的用户不能访问(根据上面的第 3 点)。
最后,代码:
路径:Auth/Module.php
namespace Auth;
use Zend\Mvc\MvcEvent;
class Module
{
public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$sm = $app->getServiceManager();
$acl = $sm->get('aclService');
$acl->initAcl();
$e -> getApplication() -> getEventManager() -> attach(MvcEvent::EVENT_ROUTE, array($acl, 'checkAcl'));
}
[...]
}
路径:Auth/src/Auth/Service/AclService.php
namespace Auth\Service;
use Zend\Permissions\Acl\Acl;
use Zend\Permissions\Acl\Role\GenericRole as Role;
use Zend\Permissions\Acl\Resource\GenericResource as Resource;
use Zend\Mvc\MvcEvent;
class AclService {
protected $authService;
protected $config;
protected $acl;
protected $role;
public function __construct($authService,$config)
{
if(!$this->acl){$this->acl=new Acl();}
$this->authService = $authService;
$this->config = $config;
}
public function initAcl()
{
$this->acl->addRole(new Role('guest'));
[...]
$this->acl->addResource(new Resource('Guest\Controller\GuestHome'));
[...]
$this->acl->allow('role', 'controller','action');
[...]
}
public function checkAcl(MvcEvent $e)
{
$controller=$e -> getRouteMatch()->getParam('controller');
$action=$e -> getRouteMatch()->getParam('action');
$role=$this->getRole();
if (!$this->acl ->isAllowed($role, $controller, $action)){
//if user isn't authenticated...
if($role=='guest'){
//set current route controller and action (this doesn't modify the url, it's non a redirect)
$e -> getRouteMatch()->setParam('controller', 'Guest\Controller\GuestHome');
$e -> getRouteMatch()->setParam('action', 'login');
}//if
//if user is authenticated but has not the permission...
else{
$response = $e -> getResponse();
$response -> getHeaders() -> addHeaderLine('Location', $e -> getRequest() -> getBaseUrl() . '/404');
$response -> setStatusCode(404);
}//else
}//if
}
public function getRole()
{
if($this->authService->hasIdentity()){
[...]
}
else{$role='guest';}
return $role;
}
[...]
}
路径:Guest/src/Guest/Controller/GuestHomeController.php
namespace Guest\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Form\Factory;
class GuestHomeController extends AbstractActionController
{
protected $authService;
protected $config;
protected $routeName;
public function __construct($authService, $config, $routeMatch)
{
$this->authService = $authService;
$this->config = $config;
$this->routeName = $routeMatch->getMatchedRouteName();
}
public function loginAction()
{
$forwardMessage=null;
//When users DOESN'T reach the form via www.site.com/login (they get forwarded), set a message to tell them what's going on
if($this->routeName!='login'){
$forwardMessage='You must be logged in to see this page!!!';
}
//This could probably be moved elsewhere and be triggered (when module is "Guest") on dispatch event with maximum priority (predispatch)
if ($this->authService->hasIdentity()) {
$this->dynamicRedirect();
}
$factory = new Factory();
$form = $factory->createForm($this->config['LoginForm']);
$request=$this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
$dataform = $form->getData();
$result=$this->authService->authenticate($dataform);
//result=status code
if($result===1){$this->dynamicRedirect();}
else{...}
}//$form->isValid()
}//$request->isPost()
$viewModel = new ViewModel();
$viewModel->setVariable('form', $form);
$viewModel->setVariable('error', $forwardMessage);
return $viewModel;
}
public function dynamicRedirect()
{
//if the form has been forwarded
if($this->routeName!='login'){
$url=$this->config['webhost'].$this->getRequest()->getRequestUri();
return $this->redirect()->toUrl($url);
}
else{return $this->redirect()->toRoute('userHome');}//else
}
[...]
}
如果我发现一些其他问题,我会更新此 post,但目前它非常有用。
P.S.: 希望我的英文成绩是可读的。 :-)
我想将未经身份验证的用户(来宾)转发到登录表单。
使用重定向时,我需要将来宾用户从他们正在查找的页面重定向到登录页面;然后再将它们重定向回来。
示例:
(访客访问)mySite/controller/action?var1=xxx&var2=yyy
(AclService 重定向)mySite/login
(AuthService 重定向)mySite/controller/action?var1=xxx&var2=yyy
所有这一切只能(我猜)使用会话变量。
我的想法是将用户转发到 mySite/login。当认证成功后,我唯一需要做的就是重定向到当前URL. 这种行为的优点是,如果用户单击浏览器的后退按钮,页面保持不变 (mySite/controller/action?var1=xxx&var2=yyy).
这是我的代码:
在module.php
public function onBootstrap(MvcEvent $e){
$app = $e->getApplication();
$sm = $app->getServiceManager();
$acl = $sm->get('AclService');
$e -> getApplication()-> getEventManager()->getSharedManager()->attach('Zend\Mvc\Controller\AbstractActionController',MvcEvent::EVENT_DISPATCH,array($acl, 'checkAcl'),-10);
}
在我的 AclService 中,checkAcl 函数
[...]
if (!$this->acl ->isAllowed($role, $controller, $action)){
//when my AuthService->hasIdentity() returns false, the the role is guest
if($role=='guest'){
$controllerClass = get_class($e->getTarget());
//this prevents nested forwards
if($controllerClass!='Auth\Controller\Auth2Controller'){
$e->setResult($e->getTarget()->forward()->dispatch('Auth\Controller\Auth2',array('action'=>'index')));
}
}
else{...}
}
然后在我的 AuthService 中,我使用这个函数(在 mysite/login 中调用)重定向经过身份验证的用户
//if the login page has ben forwarded
if($e->getRouteMatch()->getParam('controller')!='Auth\Controller\Auth2'
{
$url=$this->Config['webhost'].$e->getRequest()->getRequestUri();
return $e->getTarget()->redirect()->toUrl($url);
}
//if the request comes directly from a login/register attempt
else{return $e->getTarget()->redirect()->toRoute('user_home');}
你怎么看?有道理吗?
你知道更好的方法吗?
回答我自己的问题:
不,这没有意义。那是因为
Forwarding is a pattern to dispatch a controller when another controller has already been dispatched (Forward to another controller/action from module.php)
因此,acl 检查部分无用,因为放置在受保护页面中的任何内容都将在转发之前执行。 这意味着如果我有一个仅为经过身份验证的用户设计的用户页面(控制器)并且我调用了一个函数,如:
$name=$this->getUserName($userId);
我会收到一条错误消息,因为 var $userId 应该在身份验证过程中设置。等等...
无论如何,我找到了一个更好的解决方案,其行为完全不同但结果相同(显然没有上述问题)。
但是,由于我最初的问题非常具体,为了清楚起见,我想更好地解释一下前缀目标:
我有一个 acl 脚本,它在发送之前运行,如果用户未通过身份验证(角色=来宾),它会将用户重定向到登录表单。
当用户通过登录表单发送凭据并通过身份验证时,我想根据他们到达表单的方式执行动态重定向。
动态重定向应遵循以下条件:
当用户通过 www.site.com/login 到达表单时,一旦通过身份验证,他们应该被重定向到 www.site.com/user_home
当用户通过 www.site.com/controller/action/param?var=xxx&var2=yyy[...] 到达表单时,一旦通过身份验证,他们应该被重定向到相同的 url 不丢失任何参数。
一旦用户通过身份验证,他们应该无法访问页面 www.site.com/login(因为没有用)。这意味着寻找此页面的用户应该被重定向到其他地方。
但是我也是从假设开始的 "redirect is bad":
如果不是直接到达,浏览器历史记录中应该不会有任何www.site.com/login的踪迹。 这是因为浏览器的后退按钮可能会搞砸第 2 点(冲突)中所说的任何内容。示例:
- 我到达页面站点。com/user/post/2016?tag=foo 但我没有登录或会话已过期
- 然后我被重定向到 www.site.com/login,在那里我填写并提交了身份验证表格
- 通过身份验证后,我将再次重定向到站点。com/user/post/2016?tag=foo
- 但是,如果我通过浏览器的按钮返回,那么我的请求的来源是 www.site。com/login 并且,如第 1 点和第 3 点中所定义, 我将被重定向到 www.site.com/user_home 失去 return 到 site.com/user/post/2016?tag=foo 的能力(如果不是通过历史)。 (这就是我尝试使用 forward 插件的原因)
我发现实现此目标的解决方案是使用重定向插件以及使用 MVC 的 Routemach 的自己动手的转发过程。
我按照 samsonasik 的教程开始编码:https://samsonasik.wordpress.com/2013/05/29/zend-framework-2-working-with-authenticationservice-and-db-session-save-handler/
与samsonasik的代码的主要区别在于我添加了一个名为authService的服务,它单独处理与身份验证相关的功能。 这只是为了使代码更多 readable/reusable 因为我需要一些自定义逻辑(与我的应用程序设计相关)在身份验证时执行。
这里是我结构的主要部分:
Application
Auth
->service
->authService
->aclService
Guest
->Controller
->guestHomeController
->loginAction
->singUpAction
->...
->...
User
->Controller
->userHomeController
->...
public(角色=来宾)应该可以访问来宾模块中的任何内容,但已经通过身份验证的用户不能访问(根据上面的第 3 点)。
最后,代码:
路径:Auth/Module.php
namespace Auth;
use Zend\Mvc\MvcEvent;
class Module
{
public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$sm = $app->getServiceManager();
$acl = $sm->get('aclService');
$acl->initAcl();
$e -> getApplication() -> getEventManager() -> attach(MvcEvent::EVENT_ROUTE, array($acl, 'checkAcl'));
}
[...]
}
路径:Auth/src/Auth/Service/AclService.php
namespace Auth\Service;
use Zend\Permissions\Acl\Acl;
use Zend\Permissions\Acl\Role\GenericRole as Role;
use Zend\Permissions\Acl\Resource\GenericResource as Resource;
use Zend\Mvc\MvcEvent;
class AclService {
protected $authService;
protected $config;
protected $acl;
protected $role;
public function __construct($authService,$config)
{
if(!$this->acl){$this->acl=new Acl();}
$this->authService = $authService;
$this->config = $config;
}
public function initAcl()
{
$this->acl->addRole(new Role('guest'));
[...]
$this->acl->addResource(new Resource('Guest\Controller\GuestHome'));
[...]
$this->acl->allow('role', 'controller','action');
[...]
}
public function checkAcl(MvcEvent $e)
{
$controller=$e -> getRouteMatch()->getParam('controller');
$action=$e -> getRouteMatch()->getParam('action');
$role=$this->getRole();
if (!$this->acl ->isAllowed($role, $controller, $action)){
//if user isn't authenticated...
if($role=='guest'){
//set current route controller and action (this doesn't modify the url, it's non a redirect)
$e -> getRouteMatch()->setParam('controller', 'Guest\Controller\GuestHome');
$e -> getRouteMatch()->setParam('action', 'login');
}//if
//if user is authenticated but has not the permission...
else{
$response = $e -> getResponse();
$response -> getHeaders() -> addHeaderLine('Location', $e -> getRequest() -> getBaseUrl() . '/404');
$response -> setStatusCode(404);
}//else
}//if
}
public function getRole()
{
if($this->authService->hasIdentity()){
[...]
}
else{$role='guest';}
return $role;
}
[...]
}
路径:Guest/src/Guest/Controller/GuestHomeController.php
namespace Guest\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Form\Factory;
class GuestHomeController extends AbstractActionController
{
protected $authService;
protected $config;
protected $routeName;
public function __construct($authService, $config, $routeMatch)
{
$this->authService = $authService;
$this->config = $config;
$this->routeName = $routeMatch->getMatchedRouteName();
}
public function loginAction()
{
$forwardMessage=null;
//When users DOESN'T reach the form via www.site.com/login (they get forwarded), set a message to tell them what's going on
if($this->routeName!='login'){
$forwardMessage='You must be logged in to see this page!!!';
}
//This could probably be moved elsewhere and be triggered (when module is "Guest") on dispatch event with maximum priority (predispatch)
if ($this->authService->hasIdentity()) {
$this->dynamicRedirect();
}
$factory = new Factory();
$form = $factory->createForm($this->config['LoginForm']);
$request=$this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
$dataform = $form->getData();
$result=$this->authService->authenticate($dataform);
//result=status code
if($result===1){$this->dynamicRedirect();}
else{...}
}//$form->isValid()
}//$request->isPost()
$viewModel = new ViewModel();
$viewModel->setVariable('form', $form);
$viewModel->setVariable('error', $forwardMessage);
return $viewModel;
}
public function dynamicRedirect()
{
//if the form has been forwarded
if($this->routeName!='login'){
$url=$this->config['webhost'].$this->getRequest()->getRequestUri();
return $this->redirect()->toUrl($url);
}
else{return $this->redirect()->toRoute('userHome');}//else
}
[...]
}
如果我发现一些其他问题,我会更新此 post,但目前它非常有用。
P.S.: 希望我的英文成绩是可读的。 :-)