4

编辑:抱歉这里有大量代码;我不确定到底发生了什么,所以为了安全起见,我包含了更多内容。

我目前有一个登录页面,该页面外包给中央身份验证服务。我想对用户进行权限检查。如果用户未登录,我想将他们重定向到登录页面,并让登录页面将他们重定向到他们最初执行的任何操作,再次运行访问检查。如果他们没有权限,我想将他们重定向到拒绝访问页面。

这是我到目前为止所做的:

将此行添加到我的application.ini

resources.frontController.actionHelperPaths.Cas_Controller_Action_Helper = APPLICATION_PATH "/controllers/helpers"

创建文件$/application/controllers/helpers/PermissionRequire.php

<?php
/**
 * This class is used in order to require that a user have a given privilege before continuing.
 *
 * @copyright 2011 Case Western Reserve University, College of Arts and Sciences
 * @author Billy O'Neal III (bro4@case.edu)
 */

class Cas_Controller_Action_Helper_PermissionRequire extends Zend_Controller_Action_Helper_Abstract
{
    /**
     * Cleans up the supplied list of privileges. Strings are turned into the real privilege objects (Based on name),
     * privilege objects are left alone.
     *
     * @static
     * @param array|Privilege|string $privileges
     * @return array
     */
    private static function CleanPrivileges($privileges)
    {
        if (!is_array($privileges))
        {
            $privileges =
                    array
                    (
                        $privileges
                    );
        }
        $strings = array_filter($privileges, 'is_string');
        $objects = array_filter($privileges, function($o)
        {
            return $o instanceof Privilege;
        });
        $databaseObjects = PrivilegeQuery::create()->filterByName($strings)->find();
        return array_combine($objects, $databaseObjects);
    }

    /**
     * Generic implementation for checking whether a user can visit a page.
     * @param Privilege|string|array $privileges Any number of privileges which are required to access the given
     *                                           page. If ANY privilege is held by the user, access is allowed.
     * @param AccessControlList The acl which is being checked. Defaults to the application.
     */
    public function direct($privileges, $acl = null)
    {
        $privileges = self::CleanPrivileges($privileges);
        if ($acl === null)
        {
            $acl = AccessControlListQuery::getApplication();
        }
        $redirector = $this->getActionController()->getHelper('redirector');
        /** @var Zend_Controller_Action_Helper_Redirector $redirector */
        $redirector->setCode(307);
        if (Cas_Model_CurrentUser::IsLoggedIn() && (!Cas_Model_CurrentUser::AccessCheck($acl, $privileges)))
        {
            $redirector->gotoSimple('accessdenied', 'login');
        }
        else
        {
            $returnData = new Zend_Session_Namespace('Login');
            $returnData->params = $this->getRequest()->getParams();
            $redirector->setGotoSimple('login', 'login');
            $redirector->redirectAndExit();
        }
    }
}

这是登录控制器:

<?php

/**
 * LoginController - Controls login access for users
 */

require_once 'CAS.php';

class LoginController extends Zend_Controller_Action
{
    /**
     * Logs in to the system, and redirects to the calling action.
     *
     * @return void
     */
    public function loginAction()
    {
        //Authenticate with Login.Case.Edu.
        phpCAS::client(CAS_VERSION_2_0, 'login.case.edu', 443, '/cas', false);
        phpCAS::setNoCasServerValidation();
        phpCAS::forceAuthentication();

        $user = CaseIdUser::createFromLdap(phpCAS::getUser());
        Cas_Model_CurrentUser::SetCurrentUser($user->getSecurityIdentifier());

        $returnData = new Zend_Session_Namespace('Login');
        /** @var array $params */
        $redirector = $this->_helper->redirector;
        /** @var Zend_Controller_Action_Helper_Redirector $redirector */
        $redirector->setGotoRoute($returnData->params, 'default', true);
        $returnData->unsetAll();
        $redirector->redirectAndExit();
    }

    /**
     * Logs the user out of the system, and redirects them to the index page.
     *
     * @return void
     */
    public function logoutAction()
    {
        Cas_Model_CurrentUser::Logout();
        $this->_helper->redirector->gotoRoute('index','index', 'default', true);
    }

    /**
     * Returns an access denied view.
     *
     * @return void
     */
    public function accessdeniedAction()
    {
        //Just display the view and punt.
    }
}

问题是,在登录控制器中,当它准备将用户重定向到的 URL 时,“params”似乎是null. 此外,当控制器调用 POST 数据时,这将不起作用$this->_helper->permissionRequire(SOME PRIVILEGE)

有没有更好的方法来存储请求的整个状态,并产生与该请求完全匹配的重定向?

PS哦,这是一个使用该助手的示例控制器:

<?php

/**
 * Serves as the index page; does nothing but display views.
 */

class IndexController extends Zend_Controller_Action
{
    public function indexAction()
    {
        $renderer = $this->getHelper('ViewRenderer');
        /** @var $renderer Zend_Controller_Action_Helper_ViewRenderer */
        if (Cas_Model_CurrentUser::IsLoggedIn())
        {
            $this->_helper->permissionRequire(Cas_Model_Privilege::GetLogin());
            $this->render('loggedin');
        }
        else
        {
            $this->render('loggedout');
        }
    }
}
4

4 回答 4

2

由于您非常热衷于保存请求的 POST 状态,并且因为我自己一直在玩这个相同的想法很长时间,所以像下面这样的东西怎么样。虽然它仍然未经测试,所以我很想听听这样设置保存的请求是否真的按预期工作的结果。(暂时懒得测试这个,抱歉)。

在您的配置 ini 中:

resources.frontController.plugins[] = "Cas_Controller_Plugin_Authenticator"

这是插件:

class Cas_Controller_Plugin_Authenticator
    extends Zend_Controller_Plugin_Abstract
{
    public function routeStartup( Zend_Controller_Request_Abstract $request )
    {
        if( Zend_Auth::getInstance()->hasIdentity() )
        {
            if( null !== $request->getParam( 'from-login', null ) && Zend_Session::namespaceIsset( 'referrer' ) )
            {
                $referrer = new Zend_Session_Namespace( 'referrer' );
                if( isset( $referrer->request ) && $referrer->request instanceof Zend_Controller_Request_Abstract )
                {
                    Zend_Controller_Front::getInstance()->setRequest( $referrer->request );
                }
                Zend_Session::namespaceUnset( 'referrer' );
            }
        }
        else
        {
            $referrer = new Zend_Session_Namespace( 'referrer' );
            $referrer->request = $this->getRequest();
            return $this->_redirector->gotoRoute(
                array(
                    'module' => 'default',
                    'controller' => 'user',
                    'action' => 'login'
                ),
                'default',
                true
            );
        }
    }
}

插件应检查routeStartup用户是否经过身份验证;

  • 如果用户不是:它将当前请求对象保存在会话中并重定向到UserController::loginAction(). (见下文)
  • 如果用户是:它从会话中检索保存的请求对象(如果可用,并且如果用户刚刚登录)并替换 frontController 中的当前请求对象(我应该认为它代理路由器)。

总而言之,如果您想要更灵活地确定哪些模块/控制器/操作参数需要身份验证和授权(我想您想要),您可能希望将一些检查移至另一个钩子,而不是routeStartup:或. 因为到那时应该知道动作参数。作为一项额外的安全措施,您可能还需要比较原始请求和替换请求的操作参数(模块/控制器/操作),以确定您是否处理了正确的保存请求。routeShutdowndispatchLoopStartuppreDispatch

此外,您可能需要$request->setDispatched( false )在部分或全部挂钩中设置新的请求对象。但不完全确定:请参阅文档

这是一个示例登录控制器:

class UserController
    extends Zend_Controller_Action
{
    public function loginAction()
    {
        $request = $this->getRequest();
        if( $request->isPost() )
        {
            if( someAuthenticationProcessIsValid() )
            {
                if( Zend_Session::namespaceIsset( 'referrer' ) )
                {
                    $referrer = new Zend_Session_Namespace( 'referrer' );
                    if( isset( $referrer->request ) && $referrer->request instanceof Zend_Controller_Request_Abstract )
                    {
                        return $this->_redirector->gotoRoute(
                            array(
                                'module' => $referrer->request->getModuleName(),
                                'controller' => $referrer->request->getControllerName(),
                                'action' => $referrer->request->getActionName(),
                                'from-login' => '1'
                            ),
                            'default',
                            true
                        );
                    }   
                }

                // no referrer found, redirect to default page
                return $this->_redirector->gotoRoute(
                    array(
                        'module' => 'default',
                        'controller' => 'index',
                        'action' => 'index'
                    ),
                    'default',
                    true
                );
            }
        }

        // GET request or authentication failed, show login form again
    }
}

出于安全原因,您可能希望设置一个过期跃点为 1 的会话变量,而不是 'from-login' 查询字符串变量。

最后,说了这么多;您可能首先要彻底考虑您是否真的想要这种行为。您当然知道,POST 请求通常会禁止敏感的状态更改操作(创建、删除等)。我不确定用户在登录后(在他们的会话刚刚过期之后)通常会期望这种行为。此外,您可能需要考虑可能导致应用程序本身出现意外行为的可能场景。我现在想不出任何细节,但如果我再想一想,我相信我能想出一些。

高温高压

编辑
登录过程后忘记添加正确的重定向操作

于 2011-03-05T09:12:44.723 回答
1

当它是一个 GET 请求时,它非常简单:在缺少身份验证时,将请求 url 保存在会话中并重定向到登录。登录后,如果有保存的请求url,发给他。对于成功的身份验证,但访问权限失败,请立即将他发送到拒绝访问。

关键点是一个 POST 请求。仅保存请求 url 会丢失帖子中的数据。即使您还在会话中保存了这些参数和请求类型('POST'),登录后重定向是否可以作为 POST 请求运行?对我来说,这就是问题的本质。

一种解决方案可能是将登录后重定向到包含表单(带有method="post")的转换页面,其中所有原始帖子数据都隐藏在隐藏字段中。然后,运行一些隐藏提交按钮并提交表单的加载 javascript。在没有 javascript 的情况下,它会相对优雅地降级:用户只需单击提交按钮。

那行得通吗?

于 2011-03-04T13:37:26.867 回答
1

这不是一个编码的答案,但可能会有所帮助。当我使用 MySQL 后端实现 Zend_ACL 和 Zend_Auth 时,我遇到了类似的问题。我遇到过几次我需要一个通常无权访问控制器操作的用户才能在某种情况下获得它。Zend 已经想到了这一点,因此您可以使用断言来允许访问通常会被拒绝的访问。

然后我面临的问题是,我设置了登录超时限制 - 因此,如果用户离开计算机并且有人出现,他们将无法做任何事情,因为系统将被注销。

问题是如果他们填写了一个表格(有一些巨大的表格)并且花费的时间超过了允许的登录时间,当他们提交表格时,他们会登陆登录页面 - 如果需要 30 分钟才能完成,这很烦人表格。因此,我需要将用户重定向到接受原始表单的操作,但以某种方式将原始数据作为 POST 请求发布。我对此的原始问题。

那么我是如何解决这个问题的,也许不是最好的解决方案,但它确实有效。灵感来自这里

  • 当用户到达登录页面时,检查它是否为POST
  • 序列化POST数据并将其置于具有请求URL的会话中。
  • 一旦成功授权将请求转发到会话中保存的 URL。
  • 现在在正确的位置,检查任何序列化数据并取消序列化它,将 $_POST 设置为数据。

    这对我有用的原因是,当用户提交登录表单时,您检查用户详细信息,执行登录所需的操作,然后转发它们而不是重定向它们,这样请求仍然是 POST - 所以当您将 $_POST 数据更改为未序列化的原始数据时,页面将正常工作,甚至检查$this->getRequest()->isPost().
    我确实发现问题的地方是,我还没有尝试解决它,如果用户按下后退按钮,他们将进入登录表单,因为他们现在已经登录,所以他们不允许这样做,所以得到一个通知他们不允许在那里的消息,如果他们回到原始表单会更好 - 所以它会覆盖登录。

    如果您认为这会有所帮助,我可以尝试删减一些我使用的代码,以便确切了解我是如何做到这一点的?

  • 于 2011-03-05T10:11:31.220 回答
    0

    我已经发布了我在项目中使用的内容,以根据用户是否登录来保护一个控制器。以下代码还可用于在检查登录时检查特殊权限。如果用户没有适当的权限,重定向到错误控制器或显示错误视图。

    此代码有问题的示例 - 重定向到另一个控制器时将当前 URL 保留在参数中,并在成功时重定向回相同的 URL。

    class ProtectedController extends Zend_Controller_Action {
    
        public function indexAction() {
    
        if (Zend_Auth::getInstance()->hasIdentity()) {
                   //Show the content
           } else {
                    $this->_redirect('user/login/?success=' . $this->getRequest()->getRequestUri());
                }
            }
    
        }
    

    和用户控制器

    class UserController extends Zend_Controller_Action {
    
        public function loginAction() {
                $returnURL = $this->_getParam('success', '/');
                if (checkAuthenticity()) {
                    $this->_redirect($returnURL);
                }
          }
    }
    
    于 2011-03-04T09:36:27.477 回答