【问题标题】:Practical Zend_ACL + Zend_Auth implementation and best practices实用的 Zend_ACL + Zend_Auth 实现和最佳实践
【发布时间】:2011-01-04 00:48:29
【问题描述】:

上下文:

我的问题与我正在开发的论坛非常相似,其中有:

  1. 有权查看话题但无法回复或投票的客人
  2. 具有足够代表的成员可以编辑/投票其他线程,默认情况下他们可以回复并拥有与访客相同的权限
  3. 几乎可以做任何事情的管理员

我希望在站点范围内应用此 ACL,并默认拒绝所有资源。

我阅读了使用 Zend_Acl 的基础知识 - 因为您基本上创建角色(来宾、成员、管理员)并拒绝或允许资源(控制器、方法)到这些角色。该文档并没有非常具体地说明您应该如何在应用程序中实际实现 acl 代码,所以我去看了 SO..

遇到了一个非常有用的 stackoverflow answer from marek,它揭示了这个问题,但由于我不熟悉,我仍然无法完全理解如何在考虑到最佳实践的情况下正确实施。

海报在应用程序根目录中有一个静态文件 configAcl.php,它初始化 acl 对象、添加角色、从每个控制器中创建资源、授予 admin 访问所有内容、授予 normal 访问除admin 并将 acl 对象存储在注册表中以供以后使用。

$acl = new Zend_Acl();

$roles  = array('admin', 'normal');

// Controller script names. You have to add all of them if credential check
// is global to your application.
$controllers = array('auth', 'index', 'news', 'admin');

foreach ($roles as $role) {
    $acl->addRole(new Zend_Acl_Role($role));
}
foreach ($controllers as $controller) {
    $acl->add(new Zend_Acl_Resource($controller));
}

// Here comes credential definiton for admin user.
$acl->allow('admin'); // Has access to everything.

// Here comes credential definition for normal user.
$acl->allow('normal'); // Has access to everything...
$acl->deny('normal', 'admin'); // ... except the admin controller.

// Finally I store whole ACL definition to registry for use
// in AuthPlugin plugin.
$registry = Zend_Registry::getInstance();
$registry->set('acl', $acl);

问题 #1 - 这段代码应该在引导程序中,还是在像这样的独立文件中?如果是的话,如果在里面说,库目录会更好吗?

它的第二部分是一个扩展 Zend Controller Plugin Abstract 类的新类,它允许它与auth/login 挂钩,逻辑基本上是如果登录失败,它会重定向.. 否则它会从其中获取 acl 对象注册表,获取身份,并确定是否允许用户查看此资源。

$identity = $auth->getIdentity();

$frontController->registerPlugin(new AuthPlugin());

问题 #2 - 我将如何对实际返回用户身份的身份验证插件部分进行编码?我意识到他在下面有一些代码生成了一个 Auth 适配器 db 表对象,该对象将通过用户 ID 和凭据(散列通过检查)查询数据库表的列。我对这与 getIdentity 部分的匹配位置感到困惑。

假设我的用户表是由这些数据组成的:

user_id    user_name    level
1          superadmin   3
2          john         2
3          example.com  1

其中级别 3 = 管理员,2 = 成员,1 = 访客。

问题 #3 - 将上述身份验证代码放在哪里合适?在登录控制器内部?

问题 #4 - 另一位发帖者 replies 发表了关于如何在模型中完成 acl 逻辑的文章,但他使用的具体方法不受本机支持并且需要解决方法,是这可行吗?这真的是理想的做法吗?

【问题讨论】:

    标签: php zend-framework zend-auth zend-acl


    【解决方案1】:

    我的实现:

    问题 #1

    class App_Model_Acl extends Zend_Acl
    {   
        const ROLE_GUEST        = 'guest';
        const ROLE_USER         = 'user';
        const ROLE_PUBLISHER    = 'publisher';
        const ROLE_EDITOR       = 'editor';
        const ROLE_ADMIN        = 'admin';
        const ROLE_GOD          = 'god';
    
        protected static $_instance;
    
        /* Singleton pattern */
        protected function __construct()
        {
            $this->addRole(new Zend_Acl_Role(self::ROLE_GUEST));
            $this->addRole(new Zend_Acl_Role(self::ROLE_USER), self::ROLE_GUEST);
            $this->addRole(new Zend_Acl_Role(self::ROLE_PUBLISHER), self::ROLE_USER);
            $this->addRole(new Zend_Acl_Role(self::ROLE_EDITOR), self::ROLE_PUBLISHER);
            $this->addRole(new Zend_Acl_Role(self::ROLE_ADMIN), self::ROLE_EDITOR);
    
            //unique role for superadmin
            $this->addRole(new Zend_Acl_Role(self::ROLE_GOD));
    
            $this->allow(self::ROLE_GOD);
    
            /* Adding new resources */
            $this->add(new Zend_Acl_Resource('mvc:users'))
                 ->add(new Zend_Acl_Resource('mvc:users.auth'), 'mvc:users')
                 ->add(new Zend_Acl_Resource('mvc:users.list'), 'mvc:users');
    
            $this->allow(null, 'mvc:users', array('index', 'list'));
            $this->allow('guest', 'mvc:users.auth', array('index', 'login'));
            $this->allow('guest', 'mvc:users.list', array('index', 'list'));
            $this->deny(array('user'), 'mvc:users.auth', array('login'));
    
    
            /* Adding new resources */
            $moduleResource = new Zend_Acl_Resource('mvc:snippets');
            $this->add($moduleResource)
                 ->add(new Zend_Acl_Resource('mvc:snippets.crud'), $moduleResource)
                 ->add(new Zend_Acl_Resource('mvc:snippets.list'), $moduleResource);
    
            $this->allow(null, $moduleResource, array('index', 'list'));
            $this->allow('user', 'mvc:snippets.crud', array('create', 'update', 'delete', 'read', 'list'));
            $this->allow('guest', 'mvc:snippets.list', array('index', 'list'));
    
            return $this;
        }
    
        protected static $_user;
    
        public static function setUser(Users_Model_User $user = null)
        {
            if (null === $user) {
                throw new InvalidArgumentException('$user is null');
            }
    
            self::$_user = $user;
        }
    
        /**
         * 
         * @return App_Model_Acl
         */
        public static function getInstance()
        {
            if (null === self::$_instance) {
                self::$_instance = new self();
            }
            return self::$_instance;
        }
    
        public static function resetInstance()
        {
            self::$_instance = null;
            self::getInstance();
        }
    }
    
    
    
    class Smapp extends Bootstrap // class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
    {
        /**
         * @var App_Model_User
         */
        protected static $_currentUser;
    
        public function __construct($application)
        {
            parent::__construct($application);
        }
    
        public static function setCurrentUser(Users_Model_User $user)
        {
            self::$_currentUser = $user;
        }
    
        /**
         * @return App_Model_User
         */
        public static function getCurrentUser()
        {
            if (null === self::$_currentUser) {
                self::setCurrentUser(Users_Service_User::getUserModel());
            }
            return self::$_currentUser;
        }
    
        /**
         * @return App_Model_User
         */
        public static function getCurrentUserId()
        {
            $user = self::getCurrentUser();
            return $user->getId();
        }
    
    }
    

    在class bootstrap

    protected function _initUser()
    {
        $auth = Zend_Auth::getInstance();
        if ($auth->hasIdentity()) {
            if ($user = Users_Service_User::findOneByOpenId($auth->getIdentity())) {
                $userLastAccess = strtotime($user->last_access);
                //update the date of the last login time in 5 minutes
                if ((time() - $userLastAccess) > 60*5) {
                    $date = new Zend_Date();
                    $user->last_access = $date->toString('YYYY-MM-dd HH:mm:ss');
                    $user->save();
                }
                Smapp::setCurrentUser($user);
            }
        }
        return Smapp::getCurrentUser();
    }
    
    protected function _initAcl()
    {
        $acl = App_Model_Acl::getInstance();
        Zend_View_Helper_Navigation_HelperAbstract::setDefaultAcl($acl);
        Zend_View_Helper_Navigation_HelperAbstract::setDefaultRole(Smapp::getCurrentUser()->role);
        Zend_Registry::set('Zend_Acl', $acl);
        return $acl;
    }
    

    和Front_Controller_Plugin

    class App_Plugin_Auth extends Zend_Controller_Plugin_Abstract
    {
        private $_identity;
    
        /**
         * the acl object
         *
         * @var zend_acl
         */
        private $_acl;
    
        /**
         * the page to direct to if there is a current
         * user but they do not have permission to access
         * the resource
         *
         * @var array
         */
        private $_noacl = array('module' => 'admin',
                                 'controller' => 'error',
                                 'action' => 'no-auth');
    
        /**
         * the page to direct to if there is not current user
         *
         * @var unknown_type
         */
        private $_noauth = array('module' => 'users',
                                 'controller' => 'auth',
                                 'action' => 'login');
    
    
        /**
         * validate the current user's request
         *
         * @param zend_controller_request $request
         */
        public function preDispatch(Zend_Controller_Request_Abstract $request)
        {
            $this->_identity = Smapp::getCurrentUser();
            $this->_acl = App_Model_Acl::getInstance();
    
            if (!empty($this->_identity)) {
                $role = $this->_identity->role;
            } else {
                $role = null;
            }
    
            $controller = $request->controller;
            $module = $request->module;
            $controller = $controller;
            $action = $request->action;
    
            //go from more specific to less specific
            $moduleLevel = 'mvc:'.$module;
            $controllerLevel = $moduleLevel . '.' . $controller;
            $privelege = $action;
    
    
            if ($this->_acl->has($controllerLevel)) {
                $resource = $controllerLevel;
            } else {
                $resource = $moduleLevel;
            }
    
            if ($module != 'default' && $controller != 'index') {
                if ($this->_acl->has($resource) && !$this->_acl->isAllowed($role, $resource, $privelege)) {
                    if (!$this->_identity) {
                        $request->setModuleName($this->_noauth['module']);
                        $request->setControllerName($this->_noauth['controller']);
                        $request->setActionName($this->_noauth['action']);
                        //$request->setParam('authPage', 'login');
                    } else {
                       $request->setModuleName($this->_noacl['module']);
                       $request->setControllerName($this->_noacl['controller']);
                       $request->setActionName($this->_noacl['action']);
                       //$request->setParam('authPage', 'noauth');
                   }
                   throw new Exception('Access denied. ' . $resource . '::' . $role);
                }
            }
        }
    }
    

    最后 - Auth_Controller` :)

    class Users_AuthController extends Smapp_Controller_Action 
    {   
        //sesssion
        protected $_storage;
    
        public function getStorage()
        {
            if (null === $this->_storage) {
                $this->_storage = new Zend_Session_Namespace(__CLASS__);
            }
            return $this->_storage;
        }
    
        public function indexAction()
        {
            return $this->_forward('login');
        }
    
        public function loginAction()
        {   
            $openId = null;
            if ($this->getRequest()->isPost() and $openId = ($this->_getParam('openid_identifier', false))) {
                //do nothing
            } elseif (!isset($_GET['openid_mode'])) {
                return; 
            }
    
            //$userService = $this->loadService('User');
    
            $userService = new Users_Service_User();
    
            $result = $userService->authenticate($openId, $this->getResponse());
    
            if ($result->isValid()) {
                $identity = $result->getIdentity();
                if (!$identity['Profile']['display_name']) {
                    return $this->_helper->redirector->gotoSimpleAndExit('update', 'profile');
                }
                $this->_redirect('/');
            } else {
                $this->view->errorMessages = $result->getMessages();
            }
        }
    
        public function logoutAction()
        {
            $auth = Zend_Auth::getInstance();
            $auth->clearIdentity();
            //Zend_Session::destroy();
            $this->_redirect('/');
        }
    }
    

    问题 #2

    把它放在Zend_Auth里面。

    成功验证后 - 将身份写入存储。 $auth->getStorage()->write($result->getIdentity());

    identity - 就是user_id

    数据库设计

    CREATE TABLE `user` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `open_id` varchar(255) NOT NULL,
      `role` varchar(20) NOT NULL,
      `last_access` datetime NOT NULL,
      `created_at` datetime NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `open_id` (`open_id`)
    ) ENGINE=InnoDB  DEFAULT CHARSET=utf8
    
    CREATE TABLE `user_profile` (
      `user_id` bigint(20) NOT NULL,
      `display_name` varchar(100) DEFAULT NULL,
      `email` varchar(100) DEFAULT NULL,
      `real_name` varchar(100) DEFAULT NULL,
      `website_url` varchar(255) DEFAULT NULL,
      `location` varchar(100) DEFAULT NULL,
      `birthday` date DEFAULT NULL,
      `about_me` text,
      `view_count` int(11) NOT NULL DEFAULT '0',
      `updated_at` datetime NOT NULL,
      PRIMARY KEY (`user_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    一些糖

    /**
     * SM's code library
     * 
     * @category    
     * @package     
     * @subpackage  
     * @copyright   Copyright (c) 2009 Pavel V Egorov
     * @author      Pavel V Egorov
     * @link        http://epavel.ru/
     * @since       08.09.2009
     */
    
    
    class Smapp_View_Helper_IsAllowed extends Zend_View_Helper_Abstract
    {
        protected $_acl;
        protected $_user;
    
        public function isAllowed($resource = null, $privelege = null)
        {
            return (bool) $this->getAcl()->isAllowed($this->getUser(), $resource, $privelege);
        }
    
        /**
         * @return App_Model_Acl
         */
        public function getAcl()
        {
            if (null === $this->_acl) {
                $this->setAcl(App_Model_Acl::getInstance());
            }
            return $this->_acl;
        }
    
        /**
         * @return App_View_Helper_IsAllowed
         */
        public function setAcl(Zend_Acl $acl)
        {
            $this->_acl = $acl;
            return $this;
        }
    
        /**
         * @return Users_Model_User
         */
        public function getUser()
        {
            if (null === $this->_user) {
                $this->setUser(Smapp::getCurrentUser());
            }
            return $this->_user;
        }
    
        /**
         * @return App_View_Helper_IsAllowed
         */
        public function setUser(Users_Model_User $user)
        {
            $this->_user = $user;
            return $this;
        }
    
    }
    

    对于任何视图脚本中的类似情况

     <?php if ($this->isAllowed('mvc:snippets.crud', 'update')) : ?>
        <a title="Edit &laquo;<?=$this->escape($snippetInfo['title'])?>&raquo; snippet">Edit</a>
     <?php endif?>
    

    问题? :)

    【讨论】:

    • 太棒了,感谢您花时间粘贴所有这些内容。我会尝试实现它并让您知道,再次感谢。
    • 好的,这比我想象的要好。不过有一个问题。为什么 App_Plugin_Auth 中的 acl 对象没有从您放置它的注册表中检索到?感谢您的帖子
    • 单例从哪里获取 ACL 并不重要
    • Smapp 类应该放在哪个文件中?
    • 赞成这个答案的努力,我只是在研究 ACL 的最佳实践,因为项目需要它。这是否经过任何方式的渗透测试?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-29
    • 1970-01-01
    • 1970-01-01
    • 2015-09-28
    • 1970-01-01
    • 2020-09-18
    相关资源
    最近更新 更多