实用的Zend_ACL + Zend_Auth实现和最佳实践
语境:
我的问题与我正在开发的论坛几乎完全一样,其中有:
- 有权查看主题但无法回复或投票的访客
- 拥有足够代表权的成员可以编辑/投票其他线索,默认情况下,他们可以回复并享有与客人相同的特权
- pipe理员几乎可以做任何事情
我希望这个ACL被应用到整个站点,默认情况下拒绝所有的资源。
我阅读了使用Zend_Acl的基础知识 – 基本上创buildangular色(guest,member,admin),并拒绝或允许这些angular色的资源(控制器,方法)。 该文档不是很明确,你应该如何在你的应用程序中实际实现acl代码,所以我去看看..
马里克遇到了一个非常有用的stackoverflow 答案,在这个问题上stream露出一些亮点,但是由于我不熟悉,我仍然无法完全理解如何正确实施这个最好的实践。
海报在应用程序根目录中有一个静态文件configAcl.php
,用于初始化acl对象,添加angular色,在每个控制器之外创build一个资源,给予admin
访问所有内容,除了pipe理员之外,还可以normal
访问所有内容,并将acl对象存储registry供以后使用。
$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控制器插件抽象类的新类,它允许它被挂钩到auth/login
,逻辑基本上是在login失败时redirect。否则它从registry中获取acl对象,抓取身份,并确定是否允许用户查看此资源。
$identity = $auth->getIdentity(); $frontController->registerPlugin(new AuthPlugin());
问题2 – 如何编码实际返回用户身份的auth插件部分? 我知道他有一些代码下面生成一个身份validation适配器数据库表对象,它将通过用户ID和凭证(散列传递检查)查询数据库表的列。我很困惑,这适合于getIdentity部分。
假设我的用户表是由这些数据组成的:
user_id user_name level 1 superadmin 3 2 john 2 3 example.com 1
哪里级别3 =pipe理员,2 =成员,1 =客人。
问题3 – 在哪里放置上面的authentication代码是一个好地方? login控制器内部?
问题4 – 另一张海报回答他的文章如何在模型内部完成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(); } }
在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); } } } }
和finnaly – 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
里面。
在成功validation后在存储中写入身份。 $auth->getStorage()->write($result->getIdentity());
identity
– 只是user_id
数据库devise
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 «<?=$this->escape($snippetInfo['title'])?>» snippet">Edit</a> <?php endif?>
有问题吗? 🙂