在 Zend Framework 上处理登录页面之类的最佳方式是什么?(为什么我的实现会爆炸?) [英] What's the best way to handle something like a login page on top of Zend Framework? (And why does my implementation explode?)

查看:14
本文介绍了在 Zend Framework 上处理登录页面之类的最佳方式是什么?(为什么我的实现会爆炸?)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

抱歉这里有大量代码;我不确定到底发生了什么,所以为了安全起见,我添加了更多内容.

Sorry for the large amount of code here; I'm not sure exactly what's going on so I included more to be safe.

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

I've currently got a login page which farms out to a central authentication service. I'd like to do a permissions check on the user. If the user is not logged in, I'd like to redirect them to the login page, and have the login page redirect them to do whatever action it was they were originally doing, running the access check again. If they don't have permission, I want to redirect them to an access denied page.

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

Here's what I've done so far:

将此行添加到我的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) 时,这将不起作用.

The problem is that in the login controller when it's preparing the URL to redirect the user to, it seems "params" is null. Also, this won't work when there's POST data to the controller calling $this->_helper->permissionRequire(SOME PRIVILEGE).

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

Is there a better way of storing the entire state of a request, and coughing up a redirect which exactly matches that request?

附言哦,这是使用该助手的示例控制器:

P.S. Oh, and here's an example controller using that helper:

<?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');
        }
    }
}

推荐答案

既然你如此热衷于保存请求的 POST 状态,并且因为我自己已经玩了很长时间,同样的想法,怎么样像下面这样.尽管如此,它仍然未经测试,所以我很想听听像这样设置保存的请求是否真的按预期工作的结果.(暂时懒得测试了,抱歉).

Since you are so keen on saving the POST state of the request, and because I've been playing around with this same idea myself to for a long time, how about something like the following. It's still untested though, so I'ld love to hear the outcome of whether setting the saved request like this actually works as expected. (To lazy to test this at the moment, sorry).

在你的配置文件中:

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用户是否通过身份验证;

The plugin should check on routeStartup whether the user is authenticated;

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

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

All in all, if you want some more flexibility for determining what module/controller/action params need authentication and authorization (which I imagine you want) you probably want to move some of the checking to another hook than routeStartup: namely routeShutdown, dispatchLoopStartup or preDispatch. Because by then the action params should be known. As an extra security measure you may also want to compare the action params (module/controller/action) of the original request and the replacing request to determine if your dealing with the correct saved request.

此外,您可能需要在新请求对象的部分或全部钩子中设置 $request->setDispatched( false ).虽然不完全确定:请参阅 docs.

Furthermore, you may need to set $request->setDispatched( false ) on the new request object, in some or all of the hooks. Not entirely sure though: see the docs.

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

And here is an example login controller:

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"查询字符串变量.

For security reasons, you might want to set a session variable with an expiration hop of 1 in stead of the 'from-login' querystring variable though.

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

Finally, having said all this; you might want to thoroughly think about whether you really want this behaviour in the first place. POST requests, as you of course know, generally inhibit sensitive state changing operations (creating, deleting, etc.). I'm not sure users generally expect this behaviour right after logging in (after their session had just expired). Also, you might want to think about possible scenarios where this can lead to unexpected behaviour for the application itself. I can't think of any specifics right now, but if I gave it more thought, I'm sure I could come up with some.

HTH

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

EDIT
Forgot to add the correct redirect actions after the login process

这篇关于在 Zend Framework 上处理登录页面之类的最佳方式是什么?(为什么我的实现会爆炸?)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆