草庐IT

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

coder 2023-06-12 原文

上下文:

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

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

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

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

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

张贴者在应用程序根目录中有一个静态文件 configAcl.php,它初始化 acl 对象、添加角色、从每个 Controller 创建资源、授予 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 - 这段代码应该放在 Bootstrap 中,还是放在像这样的独立文件中?如果是的话,如果它在图书馆目录里面会更好吗?

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

$identity = $auth->getIdentity();

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

问题 #2 - 我应该如何准确地编写实际返回用户身份的 auth 插件部分?我意识到他在下面有一些代码生成了一个 Auth 适配器数据库表对象,该对象将按用户 ID 和凭据(散列通过检查)查询数据库表的列。我对它与 getIdentity 部分的配合感到困惑。

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

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

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

问题 #3 - 放置上述授权码的确切位置在哪里?在登录 Controller 内部?

问题 #4 - 另一张海报 replies在他关于如何在模型内部完成 acl 逻辑的文章中,但是他使用的特定方法不受本地支持并且需要解决方法,这可行吗?理想情况下真的应该这样做吗?

最佳答案

我的实现:

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

}

类引导

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

}

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

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

问题? :)

关于php - 实用的 Zend_ACL + Zend_Auth 实现和最佳实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2046608/

有关php - 实用的 Zend_ACL + Zend_Auth 实现和最佳实践的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  4. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  5. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  6. 使用 ACL 调用 upload_file 时出现 Ruby S3 "Access Denied"错误 - 2

    我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file

  7. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  8. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  9. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  10. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

随机推荐