php 实用 Zend_ACL + Zend_Auth 实现和最佳实践

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2046608/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 04:47:57  来源:igfitidea点击:

Practical Zend_ACL + Zend_Auth implementation and best practices

phpzend-frameworkzend-authzend-acl

提问by meder omuraliev

Context:

语境:

My questions pertain to a forum I'm developing pretty much exactly like SO, where there are:

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

  1. guests who have access to view threads but can't reply or vote
  2. members who, with enough rep, can edit/vote others threads, and by default they can reply and have the same privileges as guests
  3. admins who can pretty much do anything
  1. 有权查看话题但无法回复或投票的来宾
  2. 拥有足够代表,可以编辑/投票其他线程的成员,默认情况下他们可以回复并拥有与访客相同的权限
  3. 几乎可以做任何事情的管理员

I would want this ACL to be applied site-wide, and by default deny all resources.

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

I read the basics of using Zend_Acl - in that you basically create roles ( guest, member, admin ) and either deny or allow resources ( controllers, methods ) to those roles. The documentation isn't very specific on how you should actually implement the acl code in your application, so I went looking on SO..

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

Came across a pretty useful stackoverflow answer from marekwhich sheds some light on the issue, however due to my unfamiliarity I still can't fully grok how to properly implement this with best practices in mind.

从 marek那里找到了一个非常有用的 stackoverflow答案,它对这个问题有所了解,但是由于我不熟悉,我仍然无法完全理解如何在考虑最佳实践的情况下正确实现这一点。

The poster has a static file configAcl.phpin the application root which initializes the acl object, adds roles, creates a resource out of every controller, gives adminaccess to everything, gives normalaccess to everything but the admin and stores the acl object in the registry for later use.

海报configAcl.php在应用程序根目录中有一个静态文件,用于初始化 acl 对象、添加角色、从每个控制器创建资源、提供admin对所有内容的normal访问权限、允许访问除管理员以外的所有内容并将 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);

Question #1- Should this code be in the bootstrap, or in a standalone file such as this? If so would it be better if it was inside say, the library directory?

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

The second part of it is a new class extending the Zend Controller Plugin Abstract class which allows it to be hooked into auth/login, the logic is basically if the login fails, it redirects.. otherwise it grabs the acl object from the registry, grabs the identity, and determines if the user is allowed to view this resource.

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

$identity = $auth->getIdentity();

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

Question #2- How exactly would I code the auth plugin part that actually returns the identity of the user? I realize that he had some code below that generated a Auth adapter db table object which would query a database table's column by user id and credential ( hashed pass check ).. I'm confused on where this fits in with the getIdentity part.

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

Let's say my users table was composed of this data:

假设我的用户表由以下数据组成:

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

Where level 3 = admin, 2 = member, 1 = guest.

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

Question #3- where exactly is a good place to put the above auth code in? Inside of the login controller?

问题 #3- 究竟在哪里放置上述身份验证代码的好地方?在登录控制器内部?

Question #4- another poster replieswith his article on how the acl logic should be done inside models, yet the specific method which he uses is not natively supported and requires a workaround, is this feasible? And is this really how it ideally should be done?

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

回答by SMka

My implementation:

我的实现:

Question #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();
    }

}

in class bootstrap

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;
}

and Front_Controller_Plugin

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

and finnaly - Auth_Controller` :)

最后 - 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('/');
    }
}

Question #2

问题2

keep it inside Zend_Auth.

把它放在里面Zend_Auth

after succesfull auth - write identity in storage. $auth->getStorage()->write($result->getIdentity());

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

the identity- is simply user_id

identity-仅仅是user_id

DB design

数据库设计

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;

some sugar

一些糖

/**
 * 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;
    }

}

for things like this in any view script

对于任何视图脚本中的此类内容

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

Questions? :)

问题?:)