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 脚本,它在发送之前运行,如果用户未通过身份验证(角色=来宾),它会将用户重定向到登录表单。

当用户通过登录表单发送凭据并通过身份验证时,我想根据他们到达表单的方式执行动态重定向

动态重定向应遵循以下条件:

  1. 当用户通过 www.site.com/login 到达表单时,一旦通过身份验证,他们应该被重定向到 www.site.com/user_home

  2. 当用户通过 www.site.com/controller/action/param?var=xxx&var2=yyy[...] 到达表单时,一旦通过身份验证,他们应该被重定向到相同的 url 不丢失任何参数。

  3. 一旦用户通过身份验证,他们应该无法访问页面 www.site.com/login(因为没有用)。这意味着寻找此页面的用户应该被重定向到其他地方。

但是我也是从假设开始的 "redirect is bad":

  1. 如果不是直接到达,浏览器历史记录中应该不会有任何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.: 希望我的英文成绩是可读的。 :-)